From 9420d80b73add9f65d5ae7469e5eb82bdc710894 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 19 Sep 2025 14:08:02 -0400 Subject: zen: data share --- .../component/workspace/privacy-section.module.css | 114 +++++++++++++++++++++ .../src/component/workspace/privacy-section.tsx | 98 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 packages/console/app/src/component/workspace/privacy-section.module.css create mode 100644 packages/console/app/src/component/workspace/privacy-section.tsx (limited to 'packages/console/app/src/component') diff --git a/packages/console/app/src/component/workspace/privacy-section.module.css b/packages/console/app/src/component/workspace/privacy-section.module.css new file mode 100644 index 000000000..0bb5709cb --- /dev/null +++ b/packages/console/app/src/component/workspace/privacy-section.module.css @@ -0,0 +1,114 @@ +.root { + [data-slot="section-content"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + } + + [data-slot="reload-error"] { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + + p { + color: var(--color-danger); + font-size: var(--font-size-sm); + line-height: 1.4; + margin: 0; + flex: 1; + } + + [data-slot="create-form"] { + display: flex; + gap: var(--space-2); + margin: 0; + flex-shrink: 0; + } + } + [data-slot="payment"] { + display: flex; + flex-direction: column; + gap: var(--space-3); + padding: var(--space-4); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + min-width: 14.5rem; + width: fit-content; + + @media (max-width: 30rem) { + width: 100%; + } + + [data-slot="credit-card"] { + padding: var(--space-3-5) var(--space-4); + background-color: var(--color-bg-surface); + border-radius: var(--border-radius-sm); + display: flex; + align-items: center; + justify-content: space-between; + + [data-slot="card-icon"] { + display: flex; + align-items: center; + color: var(--color-text-muted); + } + + [data-slot="card-details"] { + display: flex; + align-items: baseline; + gap: var(--space-1); + + [data-slot="secret"] { + position: relative; + bottom: 2px; + font-size: var(--font-size-lg); + color: var(--color-text-muted); + font-weight: 400; + } + + [data-slot="number"] { + font-size: var(--font-size-3xl); + font-weight: 500; + color: var(--color-text); + } + } + } + + [data-slot="button-row"] { + display: flex; + gap: var(--space-2); + align-items: center; + + @media (max-width: 30rem) { + flex-direction: column; + + > button { + width: 100%; + } + } + + [data-slot="create-form"] { + margin: 0; + } + + /* Make Enable Billing button full width when it's the only button */ + > button { + flex: 1; + } + } + } + [data-slot="usage"] { + p { + font-size: var(--font-size-sm); + line-height: 1.5; + color: var(--color-text-secondary); + b { + font-weight: 600; + } + } + } +} diff --git a/packages/console/app/src/component/workspace/privacy-section.tsx b/packages/console/app/src/component/workspace/privacy-section.tsx new file mode 100644 index 000000000..12dd3b203 --- /dev/null +++ b/packages/console/app/src/component/workspace/privacy-section.tsx @@ -0,0 +1,98 @@ +import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" +import { withActor } from "~/context/auth.withActor" +import styles from "./billing-section.module.css" +import { Database, eq } from "@opencode/console-core/drizzle/index.js" +import { WorkspaceTable } from "@opencode/console-core/schema/workspace.sql.js" +import { Show } from "solid-js" + +const updateShare = action(async (form: FormData) => { + "use server" + const workspaceID = form.get("workspaceID")?.toString() + if (!workspaceID) return { error: "Workspace ID is required" } + const dataShare = form.get("dataShare")?.toString() === "true" ? true : null + return json( + await withActor(() => { + return Database.use((tx) => + tx + .update(WorkspaceTable) + .set({ + dataShare, + }) + .where(eq(WorkspaceTable.id, workspaceID)), + ) + }, workspaceID), + { revalidate: getWorkspaceInfo.key }, + ) +}, "workspace.disableShare") + +const getWorkspaceInfo = query(async (workspaceID: string) => { + "use server" + return withActor(() => { + return Database.use((tx) => + tx + .select({ + dataShare: WorkspaceTable.dataShare, + }) + .from(WorkspaceTable) + .where(eq(WorkspaceTable.id, workspaceID)) + .then((r) => r[0]), + ) + }, workspaceID) +}, "workspace.get") + +export function PrivacySection() { + const params = useParams() + const workspaceInfo = createAsync(() => getWorkspaceInfo(params.id)) + const updateShareSubmission = useSubmission(updateShare) + + return ( +
+
+

Privacy controls

+

+ Some providers offer data-sharing programs. If you opt in, you voluntarily share your usage data with + them, which they may use to improve their services, including model training. +

+
+

+ By opting in, you gain access to discounted pricing from the provider. You can opt in or out at any + time. +

+
+

+ + Learn more + +

+
+ +
+
+
+ You are currently opted in to the data-sharing program. +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+ ) +} -- cgit v1.2.3