mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 15:46:22 +00:00
fix: randomized tweet variations + referral fixes (#737)
* fix(agent): declare @browseros/shared as workspace dependency The agent app imports @browseros/shared/constants/urls in lib/referral/submit-referral.ts but never declared the package in its dependencies, so vite failed to resolve the import during dev. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(referral): cap daily referral earnings at 500 credits Block tweet submissions client-side once the user's balance reaches 500 to prevent unlimited credit farming via repeated shares. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(referral): randomize tweet variations for Twitter share Replace the single hardcoded share text with 10 feature-specific variations (agent mode, chat, scheduled tasks, connect apps, cowork, workflows, memory, skills, local models, ad blocking) and pick one at random each time the share button is clicked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(referral): regenerate share URL on click Previously getShareOnTwitterUrl() was evaluated once at render time as a static href, so every click produced the same tweet variation. Move the call into onClick so a new random variation is picked each time. Addresses Greptile P1 review on PR #737. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { REFERRAL_LIMITS } from '@browseros/shared/constants/limits'
|
||||||
import { ExternalLink, Loader2, Send } from 'lucide-react'
|
import { ExternalLink, Loader2, Send } from 'lucide-react'
|
||||||
import type { FC } from 'react'
|
import type { FC } from 'react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
@@ -24,8 +25,11 @@ export const ShareForCredits: FC<ShareForCreditsProps> = ({ compact }) => {
|
|||||||
const { data } = useCredits()
|
const { data } = useCredits()
|
||||||
const invalidateCredits = useInvalidateCredits()
|
const invalidateCredits = useInvalidateCredits()
|
||||||
|
|
||||||
|
const credits = data?.credits ?? 0
|
||||||
|
const atDailyMax = credits >= REFERRAL_LIMITS.MAX_DAILY_CREDITS
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!tweetUrl.trim() || !data?.browserosId) return
|
if (!tweetUrl.trim() || !data?.browserosId || atDailyMax) return
|
||||||
|
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
setResult(null)
|
setResult(null)
|
||||||
@@ -55,10 +59,22 @@ export const ShareForCredits: FC<ShareForCreditsProps> = ({ compact }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (atDailyMax) {
|
||||||
|
return (
|
||||||
|
<div className={compact ? 'space-y-2' : 'space-y-3'}>
|
||||||
|
<p className={compact ? 'text-muted-foreground text-xs' : 'text-sm'}>
|
||||||
|
You've reached the daily cap of {REFERRAL_LIMITS.MAX_DAILY_CREDITS}{' '}
|
||||||
|
credits. Come back tomorrow to earn more!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={compact ? 'space-y-2' : 'space-y-3'}>
|
<div className={compact ? 'space-y-2' : 'space-y-3'}>
|
||||||
<p className={compact ? 'text-muted-foreground text-xs' : 'text-sm'}>
|
<p className={compact ? 'text-muted-foreground text-xs' : 'text-sm'}>
|
||||||
Share BrowserOS on Twitter to earn 200 bonus credits!
|
Share BrowserOS on Twitter to earn{' '}
|
||||||
|
{REFERRAL_LIMITS.CREDITS_PER_REFERRAL} bonus credits!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<ul className="list-disc space-y-0.5 pl-4 text-muted-foreground text-xs">
|
<ul className="list-disc space-y-0.5 pl-4 text-muted-foreground text-xs">
|
||||||
@@ -67,6 +83,10 @@ export const ShareForCredits: FC<ShareForCreditsProps> = ({ compact }) => {
|
|||||||
</li>
|
</li>
|
||||||
<li>Tweet must be posted within the last 30 minutes</li>
|
<li>Tweet must be posted within the last 30 minutes</li>
|
||||||
<li>Each tweet can only be submitted once</li>
|
<li>Each tweet can only be submitted once</li>
|
||||||
|
<li>
|
||||||
|
Daily cap of {REFERRAL_LIMITS.MAX_DAILY_CREDITS} credits — resets at
|
||||||
|
midnight UTC
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<Button variant="outline" size="sm" className="w-full gap-2" asChild>
|
<Button variant="outline" size="sm" className="w-full gap-2" asChild>
|
||||||
@@ -74,6 +94,9 @@ export const ShareForCredits: FC<ShareForCreditsProps> = ({ compact }) => {
|
|||||||
href={getShareOnTwitterUrl()}
|
href={getShareOnTwitterUrl()}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.currentTarget.href = getShareOnTwitterUrl()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-3.5 w-3.5" />
|
<ExternalLink className="h-3.5 w-3.5" />
|
||||||
Share on Twitter
|
Share on Twitter
|
||||||
|
|||||||
@@ -27,7 +27,82 @@ export async function submitReferral(
|
|||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TWEET_VARIATIONS = [
|
||||||
|
`ngl @browseros_ai is kinda wild
|
||||||
|
|
||||||
|
just type what u want in plain english and it handles the annoying web shit
|
||||||
|
|
||||||
|
forms, research, data pulls... all automated
|
||||||
|
|
||||||
|
actually works`,
|
||||||
|
|
||||||
|
`been using @browseros_ai to chat with webpages lately
|
||||||
|
|
||||||
|
summarize articles, pull data, translate stuff
|
||||||
|
|
||||||
|
all happens in the same tab
|
||||||
|
|
||||||
|
no copy/paste, no switching windows
|
||||||
|
|
||||||
|
just ask and it does it`,
|
||||||
|
|
||||||
|
`wake up to @browseros_ai having already read ur emails and calendar while u were sleeping
|
||||||
|
|
||||||
|
scheduled agents are lowkey magic`,
|
||||||
|
|
||||||
|
`ngl @browseros_ai is kinda crazy
|
||||||
|
|
||||||
|
connects gmail, slack, linear, notion + 40 other apps into one ai assistant
|
||||||
|
|
||||||
|
just talk to it in plain english and it handles cross-app workflows for u
|
||||||
|
|
||||||
|
no more switching between tabs like a psycho`,
|
||||||
|
|
||||||
|
`i use @browseros_ai to automate research
|
||||||
|
|
||||||
|
it handles the browser work and drops reports straight into local folders
|
||||||
|
|
||||||
|
no switching between tools or manually saving files
|
||||||
|
|
||||||
|
just one task instead of three`,
|
||||||
|
|
||||||
|
`been messing with @browseros_ai lately
|
||||||
|
|
||||||
|
it comes with a prebuilt MCP server and I connect it claude code or codex and it just runs things for you
|
||||||
|
|
||||||
|
set it up once, use it whenever
|
||||||
|
|
||||||
|
way better than clicking through the same shit manually every time`,
|
||||||
|
|
||||||
|
`the ai actually remembers what we talked about yesterday
|
||||||
|
|
||||||
|
no more "here's the context again" every single conversation
|
||||||
|
|
||||||
|
@browseros_ai just picks up where we left off
|
||||||
|
|
||||||
|
feels like talking to someone who actually pays attention`,
|
||||||
|
|
||||||
|
`i built a skill library for my ai agent
|
||||||
|
|
||||||
|
now when i need it to do something specific, i just load the recipe i made earlier
|
||||||
|
|
||||||
|
@browseros_ai MCP is very handy`,
|
||||||
|
|
||||||
|
`been running @browseros_ai with ollama locally
|
||||||
|
|
||||||
|
everything stays on my machine, nothing gets sent out
|
||||||
|
|
||||||
|
kinda nice not having to think about what data i'm sharing`,
|
||||||
|
|
||||||
|
`switched to @browseros_ai from chrome
|
||||||
|
|
||||||
|
blocks 10x more ads and runs full ublock origin (not the lite version)
|
||||||
|
|
||||||
|
check it out`,
|
||||||
|
]
|
||||||
|
|
||||||
export function getShareOnTwitterUrl(): string {
|
export function getShareOnTwitterUrl(): string {
|
||||||
const text = 'I use @browseros_ai to browse the web with AI. Check it out!'
|
const text =
|
||||||
|
TWEET_VARIATIONS[Math.floor(Math.random() * TWEET_VARIATIONS.length)]
|
||||||
return `https://x.com/intent/tweet?text=${encodeURIComponent(text)}`
|
return `https://x.com/intent/tweet?text=${encodeURIComponent(text)}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/react": "^3.0.96",
|
"@ai-sdk/react": "^3.0.96",
|
||||||
"@browseros/server": "workspace:*",
|
"@browseros/server": "workspace:*",
|
||||||
|
"@browseros/shared": "workspace:*",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@lobehub/icons": "^2.44.0",
|
"@lobehub/icons": "^2.44.0",
|
||||||
"@mdxeditor/editor": "^3.52.4",
|
"@mdxeditor/editor": "^3.52.4",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/react": "^3.0.96",
|
"@ai-sdk/react": "^3.0.96",
|
||||||
"@browseros/server": "workspace:*",
|
"@browseros/server": "workspace:*",
|
||||||
|
"@browseros/shared": "workspace:*",
|
||||||
"@hookform/resolvers": "^5.2.2",
|
"@hookform/resolvers": "^5.2.2",
|
||||||
"@lobehub/icons": "^2.44.0",
|
"@lobehub/icons": "^2.44.0",
|
||||||
"@mdxeditor/editor": "^3.52.4",
|
"@mdxeditor/editor": "^3.52.4",
|
||||||
@@ -2210,7 +2211,7 @@
|
|||||||
|
|
||||||
"chrome-devtools-frontend": ["chrome-devtools-frontend@1.0.1577886", "", {}, "sha512-B9hY3o/0RuVCDWNYh9YnkEbRrPUMCY+NaOgBxvZRzGvqbGSMNckkVSdO67SwWR8bm4fo/qplXbUj0cSr229V6w=="],
|
"chrome-devtools-frontend": ["chrome-devtools-frontend@1.0.1577886", "", {}, "sha512-B9hY3o/0RuVCDWNYh9YnkEbRrPUMCY+NaOgBxvZRzGvqbGSMNckkVSdO67SwWR8bm4fo/qplXbUj0cSr229V6w=="],
|
||||||
|
|
||||||
"chrome-devtools-mcp": ["chrome-devtools-mcp@0.20.3", "", { "bin": { "chrome-devtools-mcp": "build/src/bin/chrome-devtools-mcp.js", "chrome-devtools": "build/src/bin/chrome-devtools.js" } }, "sha512-6MlNKlKa+J1FX9w4SUnFERF4MRGWLlrnZvIJGhhsuuMPM7qUG0F4SwheRyjwl0+tsTemxMCBHiib8mXkg5j6og=="],
|
"chrome-devtools-mcp": ["chrome-devtools-mcp@0.21.0", "", { "bin": { "chrome-devtools-mcp": "build/src/bin/chrome-devtools-mcp.js", "chrome-devtools": "build/src/bin/chrome-devtools.js" } }, "sha512-d+iqrRmcwpRFV3Q4DRCF2LCoq+WCRU3GhISKQ9v8g+1C2Uh8upj3urkjxNO4QIjhBMIYei/VQ1OQLFceby80Og=="],
|
||||||
|
|
||||||
"chrome-launcher": ["chrome-launcher@1.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q=="],
|
"chrome-launcher": ["chrome-launcher@1.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q=="],
|
||||||
|
|
||||||
|
|||||||
@@ -80,3 +80,8 @@ export const CONTENT_LIMITS = {
|
|||||||
CONSOLE_DEFAULT_LIMIT: 50,
|
CONSOLE_DEFAULT_LIMIT: 50,
|
||||||
CONSOLE_MAX_LIMIT: 200,
|
CONSOLE_MAX_LIMIT: 200,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
export const REFERRAL_LIMITS = {
|
||||||
|
MAX_DAILY_CREDITS: 500,
|
||||||
|
CREDITS_PER_REFERRAL: 200,
|
||||||
|
} as const
|
||||||
|
|||||||
Reference in New Issue
Block a user