summaryrefslogtreecommitdiffhomepage
path: root/packages/console/app/src/component
diff options
context:
space:
mode:
authorFrank <[email protected]>2025-09-19 14:08:02 -0400
committerFrank <[email protected]>2025-09-19 14:16:53 -0400
commit9420d80b73add9f65d5ae7469e5eb82bdc710894 (patch)
tree880405e710c063740df9f653db9ac78f910056d7 /packages/console/app/src/component
parentc21161b75e627ec1af22947f465ee72027918d31 (diff)
downloadopencode-9420d80b73add9f65d5ae7469e5eb82bdc710894.tar.gz
opencode-9420d80b73add9f65d5ae7469e5eb82bdc710894.zip
zen: data share
Diffstat (limited to 'packages/console/app/src/component')
-rw-r--r--packages/console/app/src/component/workspace/privacy-section.module.css114
-rw-r--r--packages/console/app/src/component/workspace/privacy-section.tsx98
2 files changed, 212 insertions, 0 deletions
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 (
+ <section class={styles.root}>
+ <div data-slot="section-title">
+ <h2>Privacy controls</h2>
+ <p>
+ Some providers offer data-sharing programs. If you opt in, you voluntarily <b>share your usage data</b> with
+ them, which they may use to improve their services, including <b>model training</b>.
+ </p>
+ <br />
+ <p>
+ By opting in, you gain access to <b>discounted pricing</b> from the provider. You can opt in or out at any
+ time.
+ </p>
+ <br />
+ <p>
+ <a target="_blank" href="/docs/zen">
+ Learn more
+ </a>
+ </p>
+ </div>
+ <Show when={workspaceInfo()?.dataShare}>
+ <div data-slot="payment">
+ <div data-slot="credit-card">
+ <div data-slot="card-details">
+ <span data-slot="number">You are currently opted in to the data-sharing program.</span>
+ </div>
+ </div>
+ </div>
+ </Show>
+ <div data-slot="section-content">
+ <div data-slot="payment">
+ <div data-slot="button-row">
+ <form action={updateShare} method="post" data-slot="create-form">
+ <input type="hidden" name="workspaceID" value={params.id} />
+ <input type="hidden" name="dataShare" value={workspaceInfo()?.dataShare ? "false" : "true"} />
+ <button data-color="ghost" type="submit" disabled={updateShareSubmission.pending}>
+ {workspaceInfo()?.dataShare
+ ? updateShareSubmission.pending
+ ? "Opting out..."
+ : "Opt out"
+ : updateShareSubmission.pending
+ ? "Opting in..."
+ : "Opt in"}
+ </button>
+ </form>
+ </div>
+ </div>
+ </div>
+ </section>
+ )
+}