mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-21 19:35:10 +00:00
core: copy Go referral links from current site
Users should share the actual site they are visiting, and the reward dialog should only close once credit is applied. Keep Drizzle tooling on the same beta snapshot as runtime ORM so schema commands avoid version drift.
This commit is contained in:
4
bun.lock
4
bun.lock
@@ -745,7 +745,7 @@
|
||||
"cross-spawn": "7.0.6",
|
||||
"diff": "8.0.2",
|
||||
"dompurify": "3.3.1",
|
||||
"drizzle-kit": "1.0.0-beta.22",
|
||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||
"effect": "4.0.0-beta.65",
|
||||
"fuzzysort": "3.1.0",
|
||||
@@ -2972,7 +2972,7 @@
|
||||
|
||||
"dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="],
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@1.0.0-beta.22", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-9HTZuQRljQKTgCx4UhiGn8KYYfHGk4+B/bRR1714W67kz0qgJvdrG527i8rQD8uUyET9UTGR1u8syySJD4znGw=="],
|
||||
"drizzle-kit": ["drizzle-kit@1.0.0-beta.19-d95b7a4", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-M0sqc+42TYBod6kEZ3AsW6+JWe3+76gR1aDFbHH5DmuLKEwewmbzlhBG6qnvV6YA1cIIbkuam3dC7r6PREOCXw=="],
|
||||
|
||||
"drizzle-orm": ["drizzle-orm@1.0.0-beta.19-d95b7a4", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-bZZKKeoRKrMVU6zKTscjrSH0+WNb1WEi3N0Jl4wEyQ7aQpTgHzdYY6IJQ1P0M74HuSJVeX4UpkFB/S6dtqLEJg=="],
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"dompurify": "3.3.1",
|
||||
"drizzle-kit": "1.0.0-beta.22",
|
||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||
"effect": "4.0.0-beta.65",
|
||||
"ai": "6.0.168",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { action, createAsync, json, query, useAction, useSubmission } from "@solidjs/router"
|
||||
import { createEffect, createMemo, createSignal, For, onCleanup, Show } from "solid-js"
|
||||
import { getRequestEvent } from "solid-js/web"
|
||||
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
||||
import { Referral } from "@opencode-ai/console-core/referral.js"
|
||||
import { withActor } from "~/context/auth.withActor"
|
||||
import { Modal } from "~/component/modal"
|
||||
@@ -11,38 +10,10 @@ import { formatResetTime, liteResetTimeKeys } from "~/lib/format-reset-time"
|
||||
import { queryLiteSubscription } from "~/routes/workspace/[id]/go/lite-section"
|
||||
import "./go-referral.css"
|
||||
|
||||
type GoReferralReward = {
|
||||
id: string
|
||||
amount: number
|
||||
email: string
|
||||
source: "inviter" | "invitee"
|
||||
status: "pending" | "available" | "applied"
|
||||
timeCreated: string | Date
|
||||
timeApplied: string | Date | null
|
||||
}
|
||||
|
||||
type GoReferralSummary = {
|
||||
inviteCode: string
|
||||
inviteUrl: string
|
||||
validInviteCount: number
|
||||
hasActiveGo: boolean
|
||||
rewardAmount: number
|
||||
totalEarned: number
|
||||
totalApplied: number
|
||||
rewards: GoReferralReward[]
|
||||
}
|
||||
|
||||
type GoReferralUsagePreview = {
|
||||
rollingUsage: GoReferralUsagePreviewItem
|
||||
weeklyUsage: GoReferralUsagePreviewItem
|
||||
monthlyUsage: GoReferralUsagePreviewItem
|
||||
}
|
||||
|
||||
type GoReferralUsagePreviewItem = {
|
||||
beforePercent: number
|
||||
afterPercent: number
|
||||
resetInSec: number
|
||||
}
|
||||
type GoReferralSummary = Awaited<ReturnType<typeof Referral.summary>>
|
||||
type GoReferralReward = GoReferralSummary["rewards"][number]
|
||||
type GoReferralUsagePreview = NonNullable<Awaited<ReturnType<typeof Referral.usagePreview>>>
|
||||
type GoReferralUsagePreviewItem = GoReferralUsagePreview["rollingUsage"]
|
||||
|
||||
const emptyUsagePreview = {
|
||||
rollingUsage: { beforePercent: 0, afterPercent: 0, resetInSec: 0 },
|
||||
@@ -52,13 +23,7 @@ const emptyUsagePreview = {
|
||||
|
||||
export const queryGoReferral = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
const summary = await Referral.summary()
|
||||
return {
|
||||
...summary,
|
||||
inviteUrl: new URL(`/go?invite=${summary.inviteCode}`, getRequestEvent()!.request.url).toString(),
|
||||
} satisfies GoReferralSummary
|
||||
}, workspaceID)
|
||||
return withActor(() => Referral.summary(), workspaceID)
|
||||
}, "go.referral.get")
|
||||
|
||||
export const queryGoReferralUsagePreview = query(async (workspaceID: string, referralID?: string) => {
|
||||
@@ -70,13 +35,7 @@ export const queryGoReferralUsagePreview = query(async (workspaceID: string, ref
|
||||
export const applyGoReferralReward = action(async (workspaceID: string, referralID: string) => {
|
||||
"use server"
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
Referral.applyReward({ referralID })
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({ error: e.message as string, data: undefined })),
|
||||
workspaceID,
|
||||
),
|
||||
await withActor(() => Referral.applyReward({ referralID }), workspaceID),
|
||||
{ revalidate: [queryGoReferral.key, queryGoReferralUsagePreview.key, queryLiteSubscription.key] },
|
||||
)
|
||||
}, "go.referral.reward.apply")
|
||||
@@ -114,10 +73,18 @@ function rewardPendingStatusKey(source: GoReferralReward["source"]) {
|
||||
function CopyInviteLink(props: { summary: GoReferralSummary }) {
|
||||
const i18n = useI18n()
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
const [origin, setOrigin] = createSignal("")
|
||||
const inviteUrl = createMemo(() => {
|
||||
const path = `/go?invite=${props.summary.inviteCode}`
|
||||
if (!origin()) return path
|
||||
return new URL(path, origin()).toString()
|
||||
})
|
||||
|
||||
onMount(() => setOrigin(window.location.origin))
|
||||
|
||||
async function copy() {
|
||||
if (typeof navigator !== "object") return
|
||||
await navigator.clipboard.writeText(props.summary.inviteUrl)
|
||||
await navigator.clipboard.writeText(inviteUrl())
|
||||
setCopied(true)
|
||||
window.setTimeout(() => setCopied(false), 1600)
|
||||
}
|
||||
@@ -125,7 +92,7 @@ function CopyInviteLink(props: { summary: GoReferralSummary }) {
|
||||
return (
|
||||
<div data-slot="invite-link-box">
|
||||
<div>
|
||||
<code title={props.summary.inviteUrl}>{props.summary.inviteUrl}</code>
|
||||
<code title={inviteUrl()}>{inviteUrl()}</code>
|
||||
<button type="button" data-color="primary" onClick={copy}>
|
||||
<Show
|
||||
when={copied()}
|
||||
@@ -187,7 +154,7 @@ export function GoReferralSection(props: { workspaceID: string; summary: GoRefer
|
||||
const reward = selected()
|
||||
if (!reward) return
|
||||
const result = await apply(props.workspaceID, reward.id)
|
||||
if (result.data) setSelected(undefined)
|
||||
if (result.applied) setSelected(undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user