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 type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
@@ -24,8 +25,11 @@ export const ShareForCredits: FC<ShareForCreditsProps> = ({ compact }) => {
|
||||
const { data } = useCredits()
|
||||
const invalidateCredits = useInvalidateCredits()
|
||||
|
||||
const credits = data?.credits ?? 0
|
||||
const atDailyMax = credits >= REFERRAL_LIMITS.MAX_DAILY_CREDITS
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!tweetUrl.trim() || !data?.browserosId) return
|
||||
if (!tweetUrl.trim() || !data?.browserosId || atDailyMax) return
|
||||
|
||||
setIsSubmitting(true)
|
||||
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 (
|
||||
<div className={compact ? 'space-y-2' : 'space-y-3'}>
|
||||
<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>
|
||||
|
||||
<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>Tweet must be posted within the last 30 minutes</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>
|
||||
|
||||
<Button variant="outline" size="sm" className="w-full gap-2" asChild>
|
||||
@@ -74,6 +94,9 @@ export const ShareForCredits: FC<ShareForCreditsProps> = ({ compact }) => {
|
||||
href={getShareOnTwitterUrl()}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => {
|
||||
e.currentTarget.href = getShareOnTwitterUrl()
|
||||
}}
|
||||
>
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
Share on Twitter
|
||||
|
||||
@@ -27,7 +27,82 @@ export async function submitReferral(
|
||||
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 {
|
||||
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)}`
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"dependencies": {
|
||||
"@ai-sdk/react": "^3.0.96",
|
||||
"@browseros/server": "workspace:*",
|
||||
"@browseros/shared": "workspace:*",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@lobehub/icons": "^2.44.0",
|
||||
"@mdxeditor/editor": "^3.52.4",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"dependencies": {
|
||||
"@ai-sdk/react": "^3.0.96",
|
||||
"@browseros/server": "workspace:*",
|
||||
"@browseros/shared": "workspace:*",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@lobehub/icons": "^2.44.0",
|
||||
"@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-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=="],
|
||||
|
||||
|
||||
@@ -80,3 +80,8 @@ export const CONTENT_LIMITS = {
|
||||
CONSOLE_DEFAULT_LIMIT: 50,
|
||||
CONSOLE_MAX_LIMIT: 200,
|
||||
} as const
|
||||
|
||||
export const REFERRAL_LIMITS = {
|
||||
MAX_DAILY_CREDITS: 500,
|
||||
CREDITS_PER_REFERRAL: 200,
|
||||
} as const
|
||||
|
||||
Reference in New Issue
Block a user