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:
vimtor
2026-05-18 20:08:43 +02:00
parent af52267914
commit 954f97b44c
3 changed files with 21 additions and 54 deletions

View File

@@ -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=="],

View File

@@ -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",

View File

@@ -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 (