feat(web): add settings root index page with category cards
All checks were successful
ci/woodpecker/push/web Pipeline was successful
All checks were successful
ci/woodpecker/push/web Pipeline was successful
Refs #466 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
239
apps/web/src/app/(authenticated)/settings/page.tsx
Normal file
239
apps/web/src/app/(authenticated)/settings/page.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
interface CategoryConfig {
|
||||
title: string;
|
||||
description: string;
|
||||
href: string;
|
||||
accent: string;
|
||||
iconBg: string;
|
||||
icon: ReactNode;
|
||||
}
|
||||
|
||||
interface SettingsCategoryCardProps {
|
||||
category: CategoryConfig;
|
||||
}
|
||||
|
||||
function SettingsCategoryCard({ category }: SettingsCategoryCardProps): ReactElement {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<Link href={category.href} style={{ textDecoration: "none" }}>
|
||||
<div
|
||||
onMouseEnter={(): void => {
|
||||
setHovered(true);
|
||||
}}
|
||||
onMouseLeave={(): void => {
|
||||
setHovered(false);
|
||||
}}
|
||||
style={{
|
||||
background: hovered ? "var(--surface-2)" : "var(--surface)",
|
||||
border: `1px solid ${hovered ? category.accent : "var(--border)"}`,
|
||||
borderRadius: "var(--r-lg)",
|
||||
padding: 20,
|
||||
transition: "background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease",
|
||||
boxShadow: hovered ? "0 4px 16px rgba(0,0,0,0.2)" : "none",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 12,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{/* Icon well */}
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: "var(--r)",
|
||||
background: category.iconBg,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: category.accent,
|
||||
transition: "transform 0.15s ease",
|
||||
transform: hovered ? "scale(1.05)" : "scale(1)",
|
||||
}}
|
||||
>
|
||||
{category.icon}
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<div style={{ fontSize: "1rem", fontWeight: 700, color: "var(--text)" }}>
|
||||
{category.title}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.83rem",
|
||||
color: "var(--muted)",
|
||||
lineHeight: 1.55,
|
||||
}}
|
||||
>
|
||||
{category.description}
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<div
|
||||
style={{
|
||||
fontSize: "0.83rem",
|
||||
color: hovered ? category.accent : "var(--muted)",
|
||||
fontWeight: 500,
|
||||
marginTop: "auto",
|
||||
transition: "color 0.15s ease",
|
||||
}}
|
||||
>
|
||||
Manage →
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
const categories: CategoryConfig[] = [
|
||||
{
|
||||
title: "Credentials",
|
||||
description:
|
||||
"Securely store and manage API keys, tokens, and passwords used by agents and integrations.",
|
||||
href: "/settings/credentials",
|
||||
accent: "var(--ms-blue-400)",
|
||||
iconBg: "rgba(47, 128, 255, 0.12)",
|
||||
icon: (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect x="5" y="9" width="10" height="8" rx="1.5" />
|
||||
<path d="M7 9V6a3 3 0 0 1 6 0v3" />
|
||||
<circle cx="10" cy="13" r="1" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Domains",
|
||||
description:
|
||||
"Organize tasks and projects by life areas or functional domains within your workspace.",
|
||||
href: "/settings/domains",
|
||||
accent: "var(--ms-teal-400)",
|
||||
iconBg: "rgba(20, 184, 166, 0.12)",
|
||||
icon: (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="10" cy="10" r="7.5" />
|
||||
<line x1="2.5" y1="10" x2="17.5" y2="10" />
|
||||
<path d="M10 2.5c2 2.5 3 5 3 7.5s-1 5-3 7.5" />
|
||||
<path d="M10 2.5c-2 2.5-3 5-3 7.5s1 5 3 7.5" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "AI Personalities",
|
||||
description:
|
||||
"Customize how the AI assistant communicates \u2014 tone, formality, and response style.",
|
||||
href: "/settings/personalities",
|
||||
accent: "var(--ms-purple-400)",
|
||||
iconBg: "rgba(139, 92, 246, 0.12)",
|
||||
icon: (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="10" cy="6" r="3" />
|
||||
<path d="M4 17c0-3.3 2.7-6 6-6s6 2.7 6 6" />
|
||||
<path d="M14 10l1.5 1.5 3-3" stroke="currentColor" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Workspaces",
|
||||
description:
|
||||
"Create and manage workspaces to organize projects and collaborate with your team.",
|
||||
href: "/settings/workspaces",
|
||||
accent: "var(--ms-amber-400)",
|
||||
iconBg: "rgba(245, 158, 11, 0.12)",
|
||||
icon: (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="10" cy="10" r="2" />
|
||||
<circle cx="4" cy="5" r="1.5" />
|
||||
<circle cx="16" cy="5" r="1.5" />
|
||||
<circle cx="16" cy="15" r="1.5" />
|
||||
<line x1="8.3" y1="8.7" x2="5.3" y2="6.2" />
|
||||
<line x1="11.7" y1="8.7" x2="14.7" y2="6.2" />
|
||||
<line x1="11.7" y1="11.3" x2="14.7" y2="13.8" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export default function SettingsPage(): ReactElement {
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto p-6">
|
||||
{/* Page header */}
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<h1
|
||||
style={{
|
||||
fontSize: "1.875rem",
|
||||
fontWeight: 700,
|
||||
color: "var(--text)",
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
</h1>
|
||||
<p
|
||||
style={{
|
||||
fontSize: "0.9rem",
|
||||
color: "var(--muted)",
|
||||
margin: "8px 0 0 0",
|
||||
}}
|
||||
>
|
||||
Configure your workspace, credentials, and preferences
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Category grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{categories.map((category) => (
|
||||
<SettingsCategoryCard key={category.href} category={category} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user