From 843f0527300a41c5147f08f9b2eab448b9bde808 Mon Sep 17 00:00:00 2001 From: Gautam25Raj Date: Tue, 19 May 2026 16:11:48 +0530 Subject: [PATCH] refactor: remove API key components and related logic - Deleted ApiKeyTypes.ts and GeneratedApiKeyCard.tsx as they are no longer needed. - Updated settings page to remove API key fetching logic and replaced it with navigation to a dedicated API keys page. - Refactored settings page layout to use a new SettingsPanel component for better organization. - Improved navigation structure in the settings page for better user experience. - Enhanced the AdditionalSections component by filtering custom sections and simplifying the rendering logic. - Introduced MasterProfileNavigator and MasterMobileSectionNav components for improved navigation in the profile section. - Added MasterSkeleton component for loading states in the profile section. - Updated utility functions for profile data sanitization and compatibility with resume templates. --- .../(dashboard)/profile/master/page.tsx | 58 +++- .../settings/components/ApiKeySection.tsx | 246 -------------- .../settings/components/ProfileCTA.tsx | 32 -- .../components/apiKeys/ApiKeyCreateForm.tsx | 118 ------- .../components/apiKeys/ApiKeyList.tsx | 111 ------- .../components/apiKeys/ApiKeyRotateModal.tsx | 202 ------------ .../components/apiKeys/ApiKeyScopePicker.tsx | 63 ---- .../components/apiKeys/ApiKeyScopes.ts | 86 ----- .../components/apiKeys/ApiKeyTypes.ts | 17 - .../apiKeys/GeneratedApiKeyCard.tsx | 44 --- .../app/(main)/(dashboard)/settings/page.tsx | 199 ++++++++---- .../components/master/AdditionalSections.tsx | 303 +++++++++--------- .../components/master/MasterProfileClient.tsx | 191 +---------- .../master/MasterProfileLoading.tsx | 16 + .../master/MasterProfileNavigator.tsx | 158 +++++++++ .../components/master/ProfileMaster.tsx | 3 +- .../components/master/master-section-nav.ts | 25 ++ .../profile/components/master/master-utils.ts | 160 ++++++++- 18 files changed, 695 insertions(+), 1337 deletions(-) delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/ApiKeySection.tsx delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/ProfileCTA.tsx delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyCreateForm.tsx delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyList.tsx delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyRotateModal.tsx delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyScopePicker.tsx delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyScopes.ts delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyTypes.ts delete mode 100644 apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/GeneratedApiKeyCard.tsx create mode 100644 apps/studio/features/profile/components/master/MasterProfileLoading.tsx create mode 100644 apps/studio/features/profile/components/master/MasterProfileNavigator.tsx create mode 100644 apps/studio/features/profile/components/master/master-section-nav.ts diff --git a/apps/studio/app/(main)/(dashboard)/profile/master/page.tsx b/apps/studio/app/(main)/(dashboard)/profile/master/page.tsx index 105208d..f6095b7 100644 --- a/apps/studio/app/(main)/(dashboard)/profile/master/page.tsx +++ b/apps/studio/app/(main)/(dashboard)/profile/master/page.tsx @@ -1,34 +1,58 @@ import type { Metadata } from "next"; +import type { LucideIcon } from "lucide-react"; + +import Link from "next/link"; +import { ArrowLeft, FileJson } from "lucide-react"; import MasterProfileClient from "@/features/profile/components/master/MasterProfileClient"; export const metadata: Metadata = { - title: `Master Editor`, - description: "Guided form experience for your global resume data.", + title: "Master Career Data", + description: "Guided editor for reusable career data.", robots: { index: false, follow: false }, }; +function QuickLink({ href, icon: Icon, label }: { href: string; icon: LucideIcon; label: string }) { + return ( + + + {label} + + ); +} + const MasterProfilePage = () => { return ( -
-
-
-
- Data Architecture -
+
+
+
+

+ Master career data +

+ +

+ Guided data editor +

-

- Master Profile Editor -

+

+ Reusable resume facts live here. User account profile remains separate. +

+
-

- The global source of truth for your professional identity. Changes here populate all - future resume drafts. -

-
+
+ + +
+ -
+ ); }; diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/ApiKeySection.tsx b/apps/studio/app/(main)/(dashboard)/settings/components/ApiKeySection.tsx deleted file mode 100644 index 8ecb838..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/ApiKeySection.tsx +++ /dev/null @@ -1,246 +0,0 @@ -"use client"; - -import { toast } from "sonner"; -import { useEffect, useState, useCallback } from "react"; - -import type { ApiKeyRecord, GeneratedApiKeyRecord } from "./apiKeys/ApiKeyTypes"; - -import { ApiRequestError, fetchApiData } from "@/utils/fetchApiData"; - -import ApiKeyList from "./apiKeys/ApiKeyList"; -import ApiKeyCreateForm from "./apiKeys/ApiKeyCreateForm"; -import ApiKeyRotateModal from "./apiKeys/ApiKeyRotateModal"; -import GeneratedApiKeyCard from "./apiKeys/GeneratedApiKeyCard"; -import { AVAILABLE_API_KEY_SCOPES } from "./apiKeys/ApiKeyScopes"; - -import DestructiveModal from "@/components/modals/DestructiveModal"; - -type ApiKeySectionProps = { - initialKeys: ApiKeyRecord[]; - initialKeysLoaded: boolean; -}; - -function resolveErrorMessage(error: unknown, fallback: string) { - if (error instanceof ApiRequestError) { - return error.message; - } - - return fallback; -} - -export default function ApiKeySection({ initialKeys, initialKeysLoaded }: ApiKeySectionProps) { - const [creating, setCreating] = useState(false); - const [loading, setLoading] = useState(!initialKeysLoaded); - - const [rateLimit, setRateLimit] = useState(20); - const [newKeyName, setNewKeyName] = useState(""); - const [keys, setKeys] = useState(initialKeys); - const [generatedKey, setGeneratedKey] = useState(null); - - const [expiresAt, setExpiresAt] = useState(""); - const [copiedId, setCopiedId] = useState(null); - const [selectedScopes, setSelectedScopes] = useState(["user:read"]); - - const [deleteSubmitting, setDeleteSubmitting] = useState(false); - const [rotateSubmitting, setRotateSubmitting] = useState(false); - const [deleteTarget, setDeleteTarget] = useState(null); - const [rotateTarget, setRotateTarget] = useState(null); - - const fetchKeys = useCallback(async () => { - try { - setLoading(true); - - const data = await fetchApiData("/api-keys", { - method: "GET", - }); - - setKeys(data); - } catch (error) { - toast.error(resolveErrorMessage(error, "Failed to fetch API keys.")); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - if (initialKeysLoaded) { - void Promise.resolve().then(() => { - setKeys(initialKeys); - setLoading(false); - }); - - return; - } - - const timer = setTimeout(() => { - void fetchKeys(); - }, 0); - - return () => clearTimeout(timer); - }, [fetchKeys, initialKeys, initialKeysLoaded]); - - const handleCreateKey = async (e: React.FormEvent) => { - e.preventDefault(); - if (!newKeyName.trim()) return; - - try { - setCreating(true); - const data = await fetchApiData("/api-keys", { - method: "POST", - body: JSON.stringify({ - name: newKeyName, - scopes: selectedScopes, - rateLimit, - expiresAt: expiresAt ? new Date(expiresAt).toISOString() : undefined, - }), - }); - - setGeneratedKey(data); - setNewKeyName(""); - setRateLimit(20); - setExpiresAt(""); - setSelectedScopes(["user:read"]); - - toast.success("API key created successfully."); - - void fetchKeys(); - } catch (error) { - toast.error(resolveErrorMessage(error, "Failed to create API key.")); - } finally { - setCreating(false); - } - }; - - const handleRotateKey = async (values: { - name: string; - rateLimit: number; - expiresAt?: string; - scopes: string[]; - }) => { - if (!rotateTarget) { - return; - } - - try { - setRotateSubmitting(true); - const data = await fetchApiData( - `/api-keys/${rotateTarget.id}/rotate`, - { - method: "POST", - body: JSON.stringify(values), - }, - ); - - setGeneratedKey(data); - setRotateTarget(null); - - toast.success("API key rotated successfully."); - - void fetchKeys(); - } catch (error) { - toast.error(resolveErrorMessage(error, "Failed to rotate API key.")); - } finally { - setRotateSubmitting(false); - } - }; - - const handleDeleteKey = async () => { - if (!deleteTarget) { - return; - } - - try { - setDeleteSubmitting(true); - - await fetchApiData(`/api-keys/${deleteTarget.id}`, { - method: "DELETE", - }); - - setDeleteTarget(null); - toast.success("API key deleted."); - void fetchKeys(); - } catch (error) { - toast.error(resolveErrorMessage(error, "Failed to delete API key.")); - } finally { - setDeleteSubmitting(false); - } - }; - - const copyToClipboard = async (text: string, id: string) => { - try { - await navigator.clipboard.writeText(text); - - setCopiedId(id); - toast.success("Copied to clipboard"); - - setTimeout(() => setCopiedId(null), 2000); - } catch { - toast.error("Failed to copy API key to clipboard."); - } - }; - - return ( -
-
-
-

API Keys

- -

- Manage your API keys for programmatic access to VeriWorkly. -

-
-
- -
- - - {generatedKey && ( - void copyToClipboard(generatedKey.key, generatedKey.id)} - onDismiss={() => setGeneratedKey(null)} - /> - )} - - - - setRotateTarget(null)} - onSubmitAction={handleRotateKey} - /> - - setDeleteTarget(null)} - onConfirmAction={handleDeleteKey} - loading={deleteSubmitting} - entityName="API key" - title="Delete API key?" - warningText="This action permanently deletes the key from the database." - description="Use this only when you no longer need the key at all. Permanent deletion cannot be undone." - /> -
-
- ); -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/ProfileCTA.tsx b/apps/studio/app/(main)/(dashboard)/settings/components/ProfileCTA.tsx deleted file mode 100644 index 552dff9..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/ProfileCTA.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import Link from "next/link"; -import { UserCircle, ArrowUpRight } from "lucide-react"; - -import { Card } from "@veriworkly/ui"; - -export default function ProfileCTA() { - return ( -
- - -
- -
- -
-
- -
- -
-

Master Profile

- -

- Centralized data used for auto-filling resume drafts. -

-
-
-
- -
- ); -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyCreateForm.tsx b/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyCreateForm.tsx deleted file mode 100644 index 6ef9bac..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyCreateForm.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { SyntheticEvent } from "react"; -import { Plus, Loader2 } from "lucide-react"; - -import { Button, Card, Input } from "@veriworkly/ui"; - -import ApiKeyScopePicker from "./ApiKeyScopePicker"; - -type ApiKeyCreateFormProps = { - creating: boolean; - newKeyName: string; - rateLimit: number; - expiresAt: string; - selectedScopes: string[]; - availableScopes: readonly string[]; - onNameChange: (value: string) => void; - onRateLimitChange: (value: number) => void; - onExpiresAtChange: (value: string) => void; - onScopesChange: (value: string[]) => void; - onSubmitAction: (e: SyntheticEvent) => void | Promise; -}; - -export default function ApiKeyCreateForm({ - creating, - newKeyName, - rateLimit, - expiresAt, - selectedScopes, - availableScopes, - onNameChange, - onRateLimitChange, - onExpiresAtChange, - onScopesChange, - onSubmitAction, -}: ApiKeyCreateFormProps) { - return ( - -
-
- - -
- onNameChange(e.target.value)} - /> - - -
-
- -
-
- - - { - const nextValue = Number(e.target.value || 1); - onRateLimitChange(Math.min(20, Math.max(1, nextValue))); - }} - /> - -

- Values above 20 are capped automatically. -

-
- -
- - - onExpiresAtChange(e.target.value)} - /> -
-
- - - -

- Leave expiry empty to use the default lifetime. Lower scopes and lower rate limits are - safer. -

- -
- ); -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyList.tsx b/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyList.tsx deleted file mode 100644 index 776cd0f..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyList.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { ArrowRightLeft, Key, Loader2, Trash2 } from "lucide-react"; - -import { Badge, Button, Card } from "@veriworkly/ui"; - -import type { ApiKeyRecord } from "./ApiKeyTypes"; -import { summarizeApiKeyScopes } from "./ApiKeyScopes"; - -type ApiKeyListProps = { - keys: ApiKeyRecord[]; - loading: boolean; - onRotate: (key: ApiKeyRecord) => void; - onDelete: (key: ApiKeyRecord) => void; -}; - -export default function ApiKeyList({ keys, loading, onRotate, onDelete }: ApiKeyListProps) { - return ( -
-

- Active Keys -

- - {loading ? ( -
- -
- ) : keys.length === 0 ? ( -
- - -

No API keys found. Generate one to get started.

-
- ) : ( -
- {keys.map((key) => ( - -
-
-
- -
- -
-
- {key.name} - - - {key.isActive ? "Active" : "Revoked"} - -
- -
- - {key.keyPrefix}...{key.keySuffix} - - - - - - {key.scopes.length} scope{key.scopes.length === 1 ? "" : "s"} - -
- -
- {summarizeApiKeyScopes(key.scopes).map((scope) => ( - - {scope.label} {scope.access} - - ))} -
- -
- Created {new Date(key.createdAt).toLocaleDateString()} - - - Expires{" "} - {key.expiresAt ? new Date(key.expiresAt).toLocaleDateString() : "never"} - -
-
-
- -
- - - -
-
-
- ))} -
- )} -
- ); -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyRotateModal.tsx b/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyRotateModal.tsx deleted file mode 100644 index 7f57fa3..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyRotateModal.tsx +++ /dev/null @@ -1,202 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { ArrowRightLeft, Calendar, Key, TrendingUp, UserPen } from "lucide-react"; - -import type { ApiKeyRecord } from "./ApiKeyTypes"; - -import { Button, Input, Modal } from "@veriworkly/ui"; - -type ApiKeyRotateModalProps = { - open: boolean; - keyRecord: ApiKeyRecord | null; - submitting: boolean; - onCloseAction: () => void; - onSubmitAction: (values: { - name: string; - rateLimit: number; - expiresAt?: string; - scopes: string[]; - }) => void; -}; - -function formatDateValue(dateValue: string | null) { - if (!dateValue) { - return ""; - } - - const parsed = new Date(dateValue); - - if (Number.isNaN(parsed.getTime())) { - return ""; - } - - return parsed.toISOString().slice(0, 10); -} - -export default function ApiKeyRotateModal({ - open, - keyRecord, - submitting, - onCloseAction, - onSubmitAction, -}: ApiKeyRotateModalProps) { - const [name, setName] = useState(""); - const [rateLimit, setRateLimit] = useState(20); - const [expiresAt, setExpiresAt] = useState(""); - - const [selectedScopes, setSelectedScopes] = useState([]); - - useEffect(() => { - if (!open || !keyRecord) { - return; - } - - void Promise.resolve().then(() => { - setName(keyRecord.name); - setSelectedScopes(keyRecord.scopes); - setExpiresAt(formatDateValue(keyRecord.expiresAt)); - setRateLimit(Math.min(20, Math.max(1, keyRecord.rateLimit || 20))); - }); - }, [open, keyRecord]); - - const handleSubmit = (event: React.SyntheticEvent) => { - event.preventDefault(); - - if (!keyRecord) { - return; - } - - onSubmitAction({ - name: name.trim() || keyRecord.name, - rateLimit: Math.min(20, Math.max(1, rateLimit)), - expiresAt: expiresAt ? new Date(expiresAt).toISOString() : undefined, - scopes: selectedScopes, - }); - }; - - const handleClose = () => { - if (!submitting) { - onCloseAction(); - } - }; - - return ( - - -
- - -
- Rotate API Key - - {keyRecord ? ( -

- Rotating{" "} - - {keyRecord.keyPrefix}...{keyRecord.keySuffix} - -

- ) : null} -
-
- -
-
-
- - - setName(event.target.value)} - /> -
-
- -
-
- - - { - const nextValue = Number(event.target.value || 1); - setRateLimit(Math.min(20, Math.max(1, nextValue))); - }} - /> -
- -
- - - setExpiresAt(event.target.value)} - /> -
-
- -
-
- - -

- The old key is revoked as soon as this replacement is created. Share the new key - only after copying it securely. -

-
-
- - - - - - -
-
-
- ); -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyScopePicker.tsx b/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyScopePicker.tsx deleted file mode 100644 index 1b43bc8..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyScopePicker.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Checkbox, Card } from "@veriworkly/ui"; - -import { API_KEY_SCOPE_OPTIONS } from "./ApiKeyScopes"; - -type ApiKeyScopePickerProps = { - availableScopes: readonly string[]; - selectedScopes: string[]; - onChange: (value: string[]) => void; -}; - -export default function ApiKeyScopePicker({ - availableScopes, - selectedScopes, - onChange, -}: ApiKeyScopePickerProps) { - const setScope = (scope: string, checked: boolean) => { - if (checked) { - onChange(Array.from(new Set([...selectedScopes, scope]))); - return; - } - - onChange(selectedScopes.filter((value) => value !== scope)); - }; - - return ( - -
-
-

Scopes

- -

- Choose exactly what this key can do. Start with the least access possible. -

-
- -
- {API_KEY_SCOPE_OPTIONS.filter((option) => availableScopes.includes(option.value)).map( - (option) => ( - - ), - )} -
-
-
- ); -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyScopes.ts b/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyScopes.ts deleted file mode 100644 index 9d6a44d..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyScopes.ts +++ /dev/null @@ -1,86 +0,0 @@ -export const AVAILABLE_API_KEY_SCOPES = [ - "user:read", - "user:write", - "resume:read", - "resume:write", - "roadmap:read", - "roadmap:write", - "github:read", - "github:write", -] as const; - -export const API_KEY_SCOPE_OPTIONS = [ - { - value: "user:read", - label: "Profile access", - description: "Read your account profile and identity details.", - }, - { - value: "user:write", - label: "Profile updates", - description: "Update user-facing profile fields.", - }, - { - value: "resume:read", - label: "Resume read", - description: "View resume content and metadata.", - }, - { - value: "resume:write", - label: "Resume write", - description: "Create, edit, and publish resume data.", - }, - { - value: "roadmap:read", - label: "Roadmap read", - description: "Inspect roadmap items and release planning data.", - }, - { - value: "roadmap:write", - label: "Roadmap write", - description: "Update roadmap items and progress states.", - }, - { - value: "github:read", - label: "GitHub read", - description: "Read synced GitHub issue and stats data.", - }, - { - value: "github:write", - label: "GitHub write", - description: "Trigger GitHub sync and write GitHub-backed data.", - }, -] as const; - -const SCOPE_GROUPS = [ - { key: "user", label: "Profile" }, - { key: "resume", label: "Resume" }, - { key: "roadmap", label: "Roadmap" }, - { key: "github", label: "GitHub" }, -] as const; - -export type ApiKeyScopeSummary = { - label: string; - access: string; -}; - -export function summarizeApiKeyScopes(scopes: string[]): ApiKeyScopeSummary[] { - const selected = new Set(scopes); - - return SCOPE_GROUPS.flatMap((group) => { - const readScope = `${group.key}:read`; - const writeScope = `${group.key}:write`; - - const hasRead = selected.has(readScope); - const hasWrite = selected.has(writeScope); - - if (!hasRead && !hasWrite) { - return []; - } - - return { - label: group.label, - access: hasRead && hasWrite ? "Read / Write" : hasWrite ? "Write" : "Read", - }; - }); -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyTypes.ts b/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyTypes.ts deleted file mode 100644 index c52ee12..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/ApiKeyTypes.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface ApiKeyRecord { - id: string; - keyPrefix: string; - keySuffix: string; - name: string; - isActive: boolean; - rateLimit: number; - scopes: string[]; - expiresAt: string | null; - revokedAt: string | null; - createdAt: string; - lastUsed: string | null; -} - -export interface GeneratedApiKeyRecord extends ApiKeyRecord { - key: string; -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/GeneratedApiKeyCard.tsx b/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/GeneratedApiKeyCard.tsx deleted file mode 100644 index eb8ea8d..0000000 --- a/apps/studio/app/(main)/(dashboard)/settings/components/apiKeys/GeneratedApiKeyCard.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { AlertCircle, Check, Copy } from "lucide-react"; - -import { Button, Card } from "@veriworkly/ui"; - -type GeneratedApiKeyCardProps = { - generatedKey: string; - copied: boolean; - onCopy: () => void; - onDismiss: () => void; -}; - -export default function GeneratedApiKeyCard({ - generatedKey, - copied, - onCopy, - onDismiss, -}: GeneratedApiKeyCardProps) { - return ( - -
-
- -

Save your API Key

-
- -

- For security reasons, this key will only be shown once. Please copy it now. -

- -
- {generatedKey} - - -
- - -
-
- ); -} diff --git a/apps/studio/app/(main)/(dashboard)/settings/page.tsx b/apps/studio/app/(main)/(dashboard)/settings/page.tsx index 9a157cf..bf60ec9 100644 --- a/apps/studio/app/(main)/(dashboard)/settings/page.tsx +++ b/apps/studio/app/(main)/(dashboard)/settings/page.tsx @@ -1,88 +1,157 @@ import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import type { LucideIcon } from "lucide-react"; -import type { ApiKeyRecord } from "./components/apiKeys/ApiKeyTypes"; +import Link from "next/link"; +import { ArrowRight, Cloud, KeyRound, Palette, ShieldCheck } from "lucide-react"; -import { backendApiUrl } from "@/lib/constants"; - -import ProfileCTA from "./components/ProfileCTA"; import SyncSection from "./components/SyncSection"; -import ApiKeySection from "./components/ApiKeySection"; import AppearanceSection from "./components/AppearanceSection"; export const metadata: Metadata = { - title: `Settings`, + title: "Settings", description: "Manage workspace defaults and behavior.", robots: { index: false, follow: false }, }; -async function fetchInitialApiKeys() { - try { - const response = await fetch(backendApiUrl("/api-keys"), { - method: "GET", - cache: "no-store", - credentials: "include", - headers: { - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - return { keys: [] as ApiKeyRecord[], loaded: false }; - } - - const payload = (await response.json()) as { - data?: ApiKeyRecord[]; - }; - - return { keys: payload.data ?? [], loaded: true }; - } catch { - return { keys: [] as ApiKeyRecord[], loaded: false }; - } -} +const settingsNav = [ + ["Appearance", "#appearance", Palette], + ["Sync", "#sync", Cloud], +] satisfies Array<[string, string, LucideIcon]>; + +function SettingsPanel({ + children, + icon: Icon, + id, + text, + title, +}: { + children: ReactNode; + icon: LucideIcon; + id: string; + text: string; + title: string; +}) { + return ( +
+
+ + + + +
+

+ {title} +

+ +

{text}

+
+
-export default async function SettingsPage() { - const initialApiKeys = await fetchInitialApiKeys(); + {children} +
+ ); +} +const SettingsPage = async () => { return ( -
-
-
-
- System Preferences +
+
+
+

Settings

+ +
+
+

+ Workspace controls +

+ +

+ Tune studio behavior without mixing in profile identity or developer tokens. +

+
+
-

Workspace

- -

- Fine-tune your environment, appearance, and cloud-synchronization behavior. -

-
- -
-
+ + +
+ -
- - - - -
+
+ + + + + + + + + + + + + + + + Developer API keys + + Create, rotate, and delete integration tokens on a dedicated page. + + + + + + +
-
+ ); -} +}; + +export default SettingsPage; diff --git a/apps/studio/features/profile/components/master/AdditionalSections.tsx b/apps/studio/features/profile/components/master/AdditionalSections.tsx index e61e157..7b0df24 100644 --- a/apps/studio/features/profile/components/master/AdditionalSections.tsx +++ b/apps/studio/features/profile/components/master/AdditionalSections.tsx @@ -1,8 +1,7 @@ import { Button } from "@veriworkly/ui"; import { Input } from "@veriworkly/ui"; -import { Select } from "@veriworkly/ui"; import { TextArea } from "@veriworkly/ui"; -import type { MasterProfileData, ResumeAdditionalItem, ResumeCustomSection } from "@/types/resume"; +import type { MasterProfileData, ResumeAdditionalItem } from "@/types/resume"; import { SectionCard, SectionItemCard } from "./master-shared"; import { @@ -602,189 +601,175 @@ export function AdditionalSections({ description="Other structures that do not fit the standard sections." >
- {localProfile.customSections.map((item) => ( - removeRepeatableItem("customSections", item.id)} - > -
- - updateRepeatableItem("customSections", item.id, (current) => ({ - ...current, - title: event.target.value, - })) - } - placeholder="Title" - /> - -