From 7fde148c8b144c42f055c4dcb43b9af6226c4e87 Mon Sep 17 00:00:00 2001 From: Ali HD <85040609+alihd-tech@users.noreply.github.com> Date: Tue, 22 Sep 2026 00:12:00 +0330 Subject: [PATCH 1/2] Redesign configurator page shell --- app/configure/page.tsx | 91 ++++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 26 deletions(-) diff --git a/app/configure/page.tsx b/app/configure/page.tsx index f8a70cb..3e74b7e 100644 --- a/app/configure/page.tsx +++ b/app/configure/page.tsx @@ -1,6 +1,12 @@ import type { Metadata } from "next" import Link from "next/link" -import { Activity, ArrowLeft, ShieldCheck } from "lucide-react" +import { + Activity, + ArrowLeft, + Boxes, + ShieldCheck, + Sparkles, +} from "lucide-react" import { PrivacyConfigurator } from "@/components/privacy-configurator" import { ThemeToggle } from "@/components/theme-toggle" @@ -13,48 +19,81 @@ export const metadata: Metadata = { export default function ConfigurePage() { return (
-
-
- - +
+
+ + - Code Life Balance + + Code Life Balance + + + Configurator + -
+ +
- Advanced GitHub App + + GitHub App - - Home + + Back
-
-
-
- - Zero-provider-access setup +
+
+
+
+ + + Privacy-first setup + + + + Live YAML + CLI + +
+

+ Configure once. Run where you trust. +

+

+ Tune the report, copy the generated workflow, and keep credentials inside GitHub + Actions or your local machine. Nothing on this page asks for your token. +

-

- Build your Code Life Balance workflow -

-

- Choose how you want the report to run. This page generates configuration only. - Your GitHub token, private activity, and generated report do not need to pass through - CodeLifeBalance infrastructure. -

-
+ +
+ {[ + ["Token", "Stays with you"], + ["Output", "SVG · JSON · MD"], + ["Setup", "No account"], + ].map(([label, value]) => ( +
+

+ {label} +

+

{value}

+
+ ))} +
+
From 83d2c079694290d4e4cc9d0be8b9140291246620 Mon Sep 17 00:00:00 2001 From: Ali HD <85040609+alihd-tech@users.noreply.github.com> Date: Tue, 22 Sep 2026 00:13:13 +0330 Subject: [PATCH 2/2] Build compact responsive configurator workspace --- components/privacy-configurator.tsx | 744 ++++++++++++++++++---------- 1 file changed, 492 insertions(+), 252 deletions(-) diff --git a/components/privacy-configurator.tsx b/components/privacy-configurator.tsx index 9b334af..32e8020 100644 --- a/components/privacy-configurator.tsx +++ b/components/privacy-configurator.tsx @@ -1,14 +1,25 @@ "use client" -import { useMemo, useState, type ComponentType, type ReactNode } from "react" import { + useMemo, + useState, + type ComponentType, + type ReactNode, +} from "react" +import { + CalendarClock, Check, + Clock3, + Code2, Copy, + FileCode2, + FolderOutput, GitBranch, LockKeyhole, + Palette, ShieldCheck, - SlidersHorizontal, Terminal, + UserRound, Workflow, } from "lucide-react" import { @@ -37,11 +48,14 @@ const timeZones = [ ] const fieldClass = - "w-full rounded-lg border border-border bg-background px-3 py-2.5 text-sm text-foreground outline-none transition focus:border-primary focus:ring-2 focus:ring-primary/15" + "h-9 w-full rounded-lg border border-border bg-background px-3 text-sm text-foreground outline-none transition placeholder:text-muted-foreground/65 focus:border-primary focus:ring-2 focus:ring-primary/15" + +type PreviewMode = "workflow" | "cli" export function PrivacyConfigurator() { const [config, setConfig] = useState(defaultPrivacyConfig) - const [copied, setCopied] = useState<"workflow" | "cli" | null>(null) + const [previewMode, setPreviewMode] = useState("workflow") + const [copied, setCopied] = useState(null) const workflow = useMemo(() => generateWorkflow(config), [config]) const cli = useMemo(() => generateCliCommand(config), [config]) @@ -61,242 +75,377 @@ export function PrivacyConfigurator() { }) } - const copy = async (kind: "workflow" | "cli", value: string) => { - await navigator.clipboard.writeText(value) + const copy = async (kind: PreviewMode, value: string) => { + try { + await navigator.clipboard.writeText(value) + } catch { + const textarea = document.createElement("textarea") + textarea.value = value + textarea.style.position = "fixed" + textarea.style.opacity = "0" + document.body.appendChild(textarea) + textarea.select() + document.execCommand("copy") + textarea.remove() + } + setCopied(kind) window.setTimeout(() => setCopied(null), 1600) } + const currentPreview = previewMode === "workflow" ? workflow : cli + const privateMode = config.includePrivate + const scheduleLabel = + config.schedule === "manual" + ? "Manual" + : config.schedule.charAt(0).toUpperCase() + config.schedule.slice(1) + return ( -
-
-
-
- -
+
+
+
-

Configure your private workflow

-

- These settings are used only to generate YAML and a local CLI command in your browser. +

Report settings

+

+ Changes update the generated setup instantly.

+ + Local only +
-
- - update("username", event.target.value)} - placeholder="octocat" - className={fieldClass} - /> - - - - update("timeZone", event.target.value)} - className={fieldClass} - /> - - {timeZones.map((zone) => ( - - - - - - - - - - - - - - - - - - - - - - - - - update("outputDir", event.target.value)} - className={fieldClass} - /> - +
+ +
+ + update("username", event.target.value)} + placeholder="octocat" + className={fieldClass} + /> + - {config.schedule === "custom" && ( -
- + update("customCron", event.target.value)} + list="code-life-timezones" + value={config.timeZone} + onChange={(event) => update("timeZone", event.target.value)} className={fieldClass} /> + + {timeZones.map((zone) => ( +
- )} -
-

Output formats

-
- {(["svg", "json", "markdown"] as ReportFormat[]).map((format) => { - const active = config.formats.includes(format) - return ( - - ) - })} +
+ + + + + + +
-
+ -
- update("commit", value)} - title="Commit generated files" - description="Write the report back to this repository. Otherwise the workflow uploads it as a GitHub artifact." - /> - update("includePrivate", value)} - title="Include private repositories" - description="Uses a user-owned CODE_LIFE_TOKEN secret. The secret remains in GitHub Actions and is never sent to CodeLifeBalance." - sensitive + +
+ update("theme", value as PrivacyConfig["theme"])} + /> + + update("cardStyle", value as PrivacyConfig["cardStyle"]) + } + /> +
+
+ + + + update("outputDir", event.target.value)} + placeholder="code-life-balance" + className={fieldClass} + /> + + +
+ Output formats +
+ {([ + ["svg", "SVG", "Card"], + ["json", "JSON", "Data"], + ["markdown", "MD", "Report"], + ] as const).map(([format, shortLabel, caption]) => { + const active = config.formats.includes(format) + return ( + + ) + })} +
+
+ +
+ update("commit", value)} + title="Commit generated files" + description="Otherwise upload the report as a GitHub artifact." + /> + update("includePrivate", value)} + title="Include private repositories" + description="Uses your own CODE_LIFE_TOKEN secret inside GitHub Actions." + sensitive + /> +
+
+ + + + update("schedule", value as PrivacyConfig["schedule"]) + } + wrap /> -
+ + {config.schedule === "custom" && ( +
+ + update("customCron", event.target.value)} + placeholder="17 3 * * 1" + className={fieldClass} + /> + +
+ )} +
+
- {config.includePrivate && ( -
-
- -
- Private mode: create a fine-grained - GitHub token with only the repositories and read permissions you want, then store it - as the repository secret CODE_LIFE_TOKEN. +
-
- copy("workflow", workflow)} - /> +
+ + + (item === "markdown" ? "MD" : item.toUpperCase())).join(" · ")} + /> + +
- copy("cli", cli)} - singleLine - /> + {privateMode && ( +
+
+ + + Private mode expects a fine-grained GitHub token stored as{" "} + CODE_LIFE_TOKEN. + +
+
+ )} + + +
-
- + - -
- + +
+ ) +} + +function ConfigSection({ + icon: Icon, + title, + description, + children, +}: { + icon: ComponentType<{ className?: string }> + title: string + description: string + children: ReactNode +}) { + return ( +
+
+ + + +
+

{title}

+

+ {description} +

+
+
+ {children}
) } @@ -312,14 +461,64 @@ function Field({ }) { return ( ) } -function Toggle({ +function ChoiceGroup({ + label, + value, + options, + onChange, + wrap = false, +}: { + label: string + value: string + options: Array<{ value: string; label: string }> + onChange: (value: string) => void + wrap?: boolean +}) { + return ( +
+ {label} +
+ {options.map((option) => { + const active = option.value === value + return ( + + ) + })} +
+
+ ) +} + +function CompactToggle({ checked, onChange, title, @@ -335,31 +534,36 @@ function Toggle({ return ( + ) +} + +function SummaryItem({ + icon: Icon, + label, value, - copied, - onCopy, - singleLine = false, }: { icon: ComponentType<{ className?: string }> - title: string - subtitle: string + label: string value: string - copied: boolean - onCopy: () => void - singleLine?: boolean }) { return ( -
-
-
- -
-

{title}

-

{subtitle}

-
-
- +
+
+ + {label} +
+

{value}

+
+ ) +} + +function CodePreview({ + mode, + value, +}: { + mode: PreviewMode + value: string +}) { + return ( +
+
+ + {mode === "workflow" + ? ".github/workflows/code-life-balance.yml" + : "terminal"} + + + {mode === "workflow" ? "YAML" : "Shell"} +
         {value}
@@ -415,7 +653,7 @@ function CodePanel({
   )
 }
 
-function TrustCard({
+function TrustNote({
   icon: Icon,
   title,
   text,
@@ -425,10 +663,12 @@ function TrustCard({
   text: string
 }) {
   return (
-    
- -

{title}

-

{text}

+
+
+ +

{title}

+
+

{text}

) }