@marlinjai/clearify 1.11.2 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,86 @@
1
- import { ClearifyConfig, ResolvedSection } from '../types/index.js';
1
+ import { z } from 'zod';
2
+ import { ClearifyConfig, ClearifyDataConfig, ResolvedSection } from '../types/index.js';
2
3
  export { defineConfig } from '../types/index.js';
3
4
 
5
+ /** Zod schema for clearify.data.json — Tier 1+2 visual-config fields, all optional. */
6
+ declare const ClearifyDataSchema: z.ZodObject<{
7
+ name: z.ZodOptional<z.ZodString>;
8
+ siteUrl: z.ZodOptional<z.ZodString>;
9
+ theme: z.ZodOptional<z.ZodObject<{
10
+ primaryColor: z.ZodOptional<z.ZodString>;
11
+ mode: z.ZodOptional<z.ZodEnum<{
12
+ light: "light";
13
+ dark: "dark";
14
+ auto: "auto";
15
+ }>>;
16
+ }, z.core.$strip>>;
17
+ logo: z.ZodOptional<z.ZodObject<{
18
+ light: z.ZodOptional<z.ZodString>;
19
+ dark: z.ZodOptional<z.ZodString>;
20
+ }, z.core.$strip>>;
21
+ links: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
22
+ sections: z.ZodOptional<z.ZodArray<z.ZodObject<{
23
+ label: z.ZodString;
24
+ docsDir: z.ZodString;
25
+ basePath: z.ZodOptional<z.ZodString>;
26
+ draft: z.ZodOptional<z.ZodBoolean>;
27
+ sitemap: z.ZodOptional<z.ZodBoolean>;
28
+ exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
29
+ git: z.ZodOptional<z.ZodObject<{
30
+ repo: z.ZodString;
31
+ ref: z.ZodOptional<z.ZodString>;
32
+ path: z.ZodOptional<z.ZodString>;
33
+ sparse: z.ZodOptional<z.ZodBoolean>;
34
+ }, z.core.$strip>>;
35
+ }, z.core.$strip>>>;
36
+ hub: z.ZodOptional<z.ZodObject<{
37
+ projects: z.ZodDefault<z.ZodArray<z.ZodObject<{
38
+ name: z.ZodString;
39
+ description: z.ZodString;
40
+ href: z.ZodOptional<z.ZodString>;
41
+ repo: z.ZodOptional<z.ZodString>;
42
+ status: z.ZodDefault<z.ZodEnum<{
43
+ active: "active";
44
+ beta: "beta";
45
+ planned: "planned";
46
+ deprecated: "deprecated";
47
+ }>>;
48
+ icon: z.ZodOptional<z.ZodString>;
49
+ tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
50
+ group: z.ZodOptional<z.ZodString>;
51
+ mode: z.ZodOptional<z.ZodEnum<{
52
+ link: "link";
53
+ embed: "embed";
54
+ inject: "inject";
55
+ }>>;
56
+ git: z.ZodOptional<z.ZodObject<{
57
+ repo: z.ZodString;
58
+ ref: z.ZodOptional<z.ZodString>;
59
+ path: z.ZodOptional<z.ZodString>;
60
+ sparse: z.ZodOptional<z.ZodBoolean>;
61
+ }, z.core.$strip>>;
62
+ embedSections: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodLiteral<"public">, z.ZodArray<z.ZodString>]>>;
63
+ injectInto: z.ZodOptional<z.ZodString>;
64
+ docsPath: z.ZodOptional<z.ZodString>;
65
+ }, z.core.$strip>>>;
66
+ scan: z.ZodOptional<z.ZodString>;
67
+ cacheDir: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strip>>;
69
+ }, z.core.$strip>;
70
+ /**
71
+ * Deep-merge utility where JSON (overlay) values win over base values.
72
+ * Plain objects merge recursively. Arrays are replaced entirely.
73
+ * Undefined values in the overlay are skipped.
74
+ */
75
+ declare function deepMergeJsonWins<T extends Record<string, any>>(base: T, json: Partial<T>): T;
76
+ /** Load and validate clearify.data.json from a project root. Returns `{}` on missing/invalid file. */
77
+ declare function loadDataConfig(root: string): ClearifyDataConfig;
78
+ /** Validate and atomically write clearify.data.json. */
79
+ declare function writeDataConfig(root: string, data: unknown): void;
4
80
  declare const defaultConfig: ClearifyConfig;
5
81
  declare function loadUserConfig(root: string): Promise<Partial<ClearifyConfig>>;
6
82
  declare function resolveConfig(userConfig?: Partial<ClearifyConfig>, root?: string): ClearifyConfig;
7
83
  declare function resolveSections(config: ClearifyConfig, root: string): Promise<ResolvedSection[]>;
8
84
  declare function scanHubProjects(config: ClearifyConfig, root: string): Promise<ClearifyConfig>;
9
85
 
10
- export { defaultConfig, loadUserConfig, resolveConfig, resolveSections, scanHubProjects };
86
+ export { ClearifyDataSchema, deepMergeJsonWins, defaultConfig, loadDataConfig, loadUserConfig, resolveConfig, resolveSections, scanHubProjects, writeDataConfig };
@@ -1,19 +1,27 @@
1
1
  import {
2
+ ClearifyDataSchema,
3
+ deepMergeJsonWins,
2
4
  defaultConfig,
5
+ loadDataConfig,
3
6
  loadUserConfig,
4
7
  resolveConfig,
5
8
  resolveSections,
6
- scanHubProjects
7
- } from "../chunk-PVTUVUBK.js";
9
+ scanHubProjects,
10
+ writeDataConfig
11
+ } from "../chunk-2WLFSQS7.js";
8
12
  import "../chunk-AJBCHKBP.js";
9
13
  import {
10
14
  defineConfig
11
15
  } from "../chunk-V7LLYIRO.js";
12
16
  export {
17
+ ClearifyDataSchema,
18
+ deepMergeJsonWins,
13
19
  defaultConfig,
14
20
  defineConfig,
21
+ loadDataConfig,
15
22
  loadUserConfig,
16
23
  resolveConfig,
17
24
  resolveSections,
18
- scanHubProjects
25
+ scanHubProjects,
26
+ writeDataConfig
19
27
  };
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  buildSite
3
- } from "../chunk-EMM4VLF7.js";
3
+ } from "../chunk-6YAEHWC7.js";
4
4
  import "../chunk-PRTER35L.js";
5
5
  import "../chunk-SCZZB7OE.js";
6
- import "../chunk-PFX7LHRZ.js";
6
+ import "../chunk-XYS5SV4M.js";
7
7
  import "../chunk-B2Q23JW3.js";
8
- import "../chunk-PVTUVUBK.js";
8
+ import "../chunk-2WLFSQS7.js";
9
9
  import "../chunk-AJBCHKBP.js";
10
10
  import "../chunk-V7LLYIRO.js";
11
11
  import "../chunk-QTGT76RY.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  checkFrontmatter,
3
3
  checkLinks
4
- } from "../chunk-NGWAMBXS.js";
5
- import "../chunk-PVTUVUBK.js";
4
+ } from "../chunk-6XHVO5AO.js";
5
+ import "../chunk-2WLFSQS7.js";
6
6
  import "../chunk-AJBCHKBP.js";
7
7
  import "../chunk-V7LLYIRO.js";
8
8
  import "../chunk-QTGT76RY.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  buildSite
3
- } from "../chunk-EMM4VLF7.js";
3
+ } from "../chunk-6YAEHWC7.js";
4
4
  import {
5
5
  remarkMermaidToComponent
6
6
  } from "../chunk-PRTER35L.js";
7
7
  import "../chunk-SCZZB7OE.js";
8
8
  import {
9
9
  clearifyPlugin
10
- } from "../chunk-PFX7LHRZ.js";
10
+ } from "../chunk-XYS5SV4M.js";
11
11
  import "../chunk-B2Q23JW3.js";
12
12
  import {
13
13
  init
@@ -15,11 +15,11 @@ import {
15
15
  import {
16
16
  checkFrontmatter,
17
17
  checkLinks
18
- } from "../chunk-NGWAMBXS.js";
18
+ } from "../chunk-6XHVO5AO.js";
19
19
  import {
20
20
  loadUserConfig,
21
21
  resolveConfig
22
- } from "../chunk-PVTUVUBK.js";
22
+ } from "../chunk-2WLFSQS7.js";
23
23
  import "../chunk-AJBCHKBP.js";
24
24
  import "../chunk-V7LLYIRO.js";
25
25
  import "../chunk-QTGT76RY.js";
@@ -102,15 +102,36 @@ interface HubProject {
102
102
  group?: string;
103
103
  hubUrl?: string;
104
104
  hubName?: string;
105
- mode?: 'link' | 'embed';
105
+ mode?: 'link' | 'embed' | 'inject';
106
106
  git?: RemoteGitSource;
107
107
  embedSections?: 'all' | 'public' | string[];
108
+ injectInto?: string;
109
+ docsPath?: string;
108
110
  }
109
111
  interface HubConfig {
110
112
  projects: HubProject[];
111
113
  scan?: string;
112
114
  cacheDir?: string;
113
115
  }
116
+ /** Tier 1+2 fields that can live in clearify.data.json for visual editing. */
117
+ interface ClearifyDataConfig {
118
+ name?: string;
119
+ siteUrl?: string;
120
+ theme?: {
121
+ primaryColor?: string;
122
+ mode?: 'light' | 'dark' | 'auto';
123
+ };
124
+ logo?: {
125
+ light?: string;
126
+ dark?: string;
127
+ };
128
+ links?: {
129
+ github?: string;
130
+ [key: string]: string | undefined;
131
+ };
132
+ sections?: SectionConfig[];
133
+ hub?: HubConfig;
134
+ }
114
135
  declare function defineConfig(config: Partial<ClearifyConfig>): Partial<ClearifyConfig>;
115
136
 
116
- export { type ClearifyConfig, type DocCategory, type HubConfig, type HubProject, type NavigationItem, type PageFrontmatter, type RemoteGitSource, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
137
+ export { type ClearifyConfig, type ClearifyDataConfig, type DocCategory, type HubConfig, type HubProject, type NavigationItem, type PageFrontmatter, type RemoteGitSource, type ResolvedSection, type RouteEntry, type SectionConfig, type SectionNavigation, defineConfig };
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  clearifyPlugin
3
- } from "../chunk-PFX7LHRZ.js";
3
+ } from "../chunk-XYS5SV4M.js";
4
4
  import "../chunk-B2Q23JW3.js";
5
- import "../chunk-PVTUVUBK.js";
5
+ import "../chunk-2WLFSQS7.js";
6
6
  import "../chunk-AJBCHKBP.js";
7
7
  import "../chunk-V7LLYIRO.js";
8
8
  import "../chunk-QTGT76RY.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marlinjai/clearify",
3
- "version": "1.11.2",
3
+ "version": "1.12.0",
4
4
  "description": "An open-source documentation site generator. Turn markdown into beautiful docs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,8 @@
14
14
  "./config": {
15
15
  "types": "./dist/node/core/config.d.ts",
16
16
  "import": "./dist/node/core/config.js"
17
- }
17
+ },
18
+ "./config-schema.json": "./dist/config-schema.json"
18
19
  },
19
20
  "files": [
20
21
  "bin",
@@ -24,6 +25,7 @@
24
25
  ],
25
26
  "scripts": {
26
27
  "build": "tsup",
28
+ "postbuild": "node scripts/generate-schema.mjs",
27
29
  "dev:self": "node bin/clearify.js dev",
28
30
  "prepare": "husky",
29
31
  "release": "semantic-release"
@@ -7,12 +7,20 @@ import routes from 'virtual:clearify/routes';
7
7
  import config from 'virtual:clearify/config';
8
8
  // @ts-expect-error virtual module
9
9
  import sections from 'virtual:clearify/navigation';
10
+ // @ts-expect-error virtual module
11
+ import adminEnabled from 'virtual:clearify/admin-enabled';
10
12
  import { Layout } from '../theme/Layout.js';
11
13
  import { Head } from '../theme/Head.js';
12
14
  import { NotFound } from '../theme/NotFound.js';
13
15
  import { mdxComponents } from '../theme/MDXComponents.js';
14
16
  import '../theme/styles/globals.css';
15
17
 
18
+ const AdminLayout = React.lazy(() => import('../theme/admin/AdminLayout.js'));
19
+ const AdminDashboard = React.lazy(() => import('../theme/admin/AdminDashboard.js'));
20
+ const ProjectsManager = React.lazy(() => import('../theme/admin/ProjectsManager.js'));
21
+ const SectionsManager = React.lazy(() => import('../theme/admin/SectionsManager.js'));
22
+ const SiteSettings = React.lazy(() => import('../theme/admin/SiteSettings.js'));
23
+
16
24
  interface RouteEntry {
17
25
  path: string;
18
26
  component?: () => Promise<any>;
@@ -74,24 +82,59 @@ function PageWrapper({ loader, fallbackFrontmatter }: { loader: () => Promise<an
74
82
  );
75
83
  }
76
84
 
85
+ function AdminDisabled() {
86
+ return (
87
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', color: 'var(--clearify-text-secondary)', fontFamily: 'var(--font-sans)' }}>
88
+ Admin panel is only available in dev mode.
89
+ </div>
90
+ );
91
+ }
92
+
77
93
  function AppRoutes() {
78
94
  return (
79
- <Layout config={config} sections={sections}>
80
- <Routes>
81
- {(routes as RouteEntry[]).map((route) =>
82
- route.redirectTo ? (
83
- <Route key={route.path} path={route.path} element={<Navigate to={route.redirectTo} replace />} />
84
- ) : (
85
- <Route
86
- key={route.path}
87
- path={route.path}
88
- element={<PageWrapper loader={route.component!} fallbackFrontmatter={route.frontmatter} />}
89
- />
90
- )
91
- )}
92
- <Route path="*" element={<NotFound />} />
93
- </Routes>
94
- </Layout>
95
+ <Routes>
96
+ {/* Admin routes — outside Layout, lazy-loaded */}
97
+ {adminEnabled ? (
98
+ <Route
99
+ path="/admin"
100
+ element={
101
+ <React.Suspense fallback={<div style={{ padding: '2rem', color: 'var(--clearify-text-secondary)' }}>Loading...</div>}>
102
+ <AdminLayout />
103
+ </React.Suspense>
104
+ }
105
+ >
106
+ <Route index element={<AdminDashboard />} />
107
+ <Route path="projects" element={<ProjectsManager />} />
108
+ <Route path="sections" element={<SectionsManager />} />
109
+ <Route path="settings" element={<SiteSettings />} />
110
+ </Route>
111
+ ) : (
112
+ <Route path="/admin/*" element={<AdminDisabled />} />
113
+ )}
114
+
115
+ {/* Docs routes — wrapped in Layout */}
116
+ <Route
117
+ path="*"
118
+ element={
119
+ <Layout config={config} sections={sections}>
120
+ <Routes>
121
+ {(routes as RouteEntry[]).map((route) =>
122
+ route.redirectTo ? (
123
+ <Route key={route.path} path={route.path} element={<Navigate to={route.redirectTo} replace />} />
124
+ ) : (
125
+ <Route
126
+ key={route.path}
127
+ path={route.path}
128
+ element={<PageWrapper loader={route.component!} fallbackFrontmatter={route.frontmatter} />}
129
+ />
130
+ )
131
+ )}
132
+ <Route path="*" element={<NotFound />} />
133
+ </Routes>
134
+ </Layout>
135
+ }
136
+ />
137
+ </Routes>
95
138
  );
96
139
  }
97
140
 
@@ -2,6 +2,8 @@ import React from 'react';
2
2
  import { Link } from 'react-router-dom';
3
3
  import { useTheme } from './ThemeProvider.js';
4
4
  import { Search } from './Search.js';
5
+ // @ts-expect-error virtual module
6
+ import adminEnabled from 'virtual:clearify/admin-enabled';
5
7
 
6
8
  interface HeaderProps {
7
9
  name: string;
@@ -158,6 +160,36 @@ export function Header({ name, links, logo, onMenuToggle }: HeaderProps) {
158
160
  </a>
159
161
  )}
160
162
 
163
+ {adminEnabled && (
164
+ <Link
165
+ to="/admin"
166
+ aria-label="Admin panel"
167
+ style={{
168
+ display: 'inline-flex',
169
+ alignItems: 'center',
170
+ justifyContent: 'center',
171
+ width: 36,
172
+ height: 36,
173
+ borderRadius: 'var(--clearify-radius-sm)',
174
+ color: 'var(--clearify-text-secondary)',
175
+ transition: 'color 0.15s, background-color 0.15s',
176
+ }}
177
+ onMouseEnter={(e) => {
178
+ e.currentTarget.style.color = 'var(--clearify-text)';
179
+ e.currentTarget.style.backgroundColor = 'var(--clearify-bg-secondary)';
180
+ }}
181
+ onMouseLeave={(e) => {
182
+ e.currentTarget.style.color = 'var(--clearify-text-secondary)';
183
+ e.currentTarget.style.backgroundColor = 'transparent';
184
+ }}
185
+ >
186
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
187
+ <circle cx="12" cy="12" r="3" />
188
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
189
+ </svg>
190
+ </Link>
191
+ )}
192
+
161
193
  <button
162
194
  onClick={toggleTheme}
163
195
  style={{
@@ -0,0 +1,129 @@
1
+ import React from 'react';
2
+ import { Link } from 'react-router-dom';
3
+
4
+ const cardStyle: React.CSSProperties = {
5
+ backgroundColor: 'var(--clearify-surface-elevated)',
6
+ border: '1px solid var(--clearify-border)',
7
+ borderRadius: 'var(--clearify-radius)',
8
+ padding: '1.5rem',
9
+ boxShadow: 'var(--clearify-shadow)',
10
+ textDecoration: 'none',
11
+ color: 'inherit',
12
+ transition: 'box-shadow 0.15s, border-color 0.15s',
13
+ };
14
+
15
+ interface DashboardCardProps {
16
+ title: string;
17
+ description: string;
18
+ to: string;
19
+ icon: React.ReactNode;
20
+ }
21
+
22
+ function DashboardCard({ title, description, to, icon }: DashboardCardProps) {
23
+ return (
24
+ <Link
25
+ to={to}
26
+ style={cardStyle}
27
+ onMouseEnter={(e) => {
28
+ e.currentTarget.style.boxShadow = 'var(--clearify-shadow-glow)';
29
+ e.currentTarget.style.borderColor = 'var(--clearify-primary)';
30
+ }}
31
+ onMouseLeave={(e) => {
32
+ e.currentTarget.style.boxShadow = 'var(--clearify-shadow)';
33
+ e.currentTarget.style.borderColor = 'var(--clearify-border)';
34
+ }}
35
+ >
36
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '0.75rem' }}>
37
+ <div
38
+ style={{
39
+ width: 40,
40
+ height: 40,
41
+ borderRadius: 'var(--clearify-radius-sm)',
42
+ background: 'var(--clearify-primary-soft)',
43
+ display: 'flex',
44
+ alignItems: 'center',
45
+ justifyContent: 'center',
46
+ color: 'var(--clearify-primary)',
47
+ }}
48
+ >
49
+ {icon}
50
+ </div>
51
+ <h3 style={{ fontSize: '1.125rem', fontWeight: 650, color: 'var(--clearify-text)' }}>
52
+ {title}
53
+ </h3>
54
+ </div>
55
+ <p style={{ fontSize: '0.875rem', color: 'var(--clearify-text-secondary)', lineHeight: 1.5 }}>
56
+ {description}
57
+ </p>
58
+ </Link>
59
+ );
60
+ }
61
+
62
+ export function AdminDashboard() {
63
+ return (
64
+ <div style={{ padding: '2rem', maxWidth: '64rem', margin: '0 auto' }}>
65
+ <h1
66
+ style={{
67
+ fontSize: '2rem',
68
+ fontWeight: 800,
69
+ letterSpacing: '-0.035em',
70
+ color: 'var(--clearify-text)',
71
+ marginBottom: '0.5rem',
72
+ }}
73
+ >
74
+ Admin Dashboard
75
+ </h1>
76
+ <p style={{ color: 'var(--clearify-text-secondary)', marginBottom: '2rem' }}>
77
+ Manage your Clearify documentation site.
78
+ </p>
79
+
80
+ <div
81
+ style={{
82
+ display: 'grid',
83
+ gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
84
+ gap: '1.25rem',
85
+ }}
86
+ >
87
+ <DashboardCard
88
+ title="Projects"
89
+ description="Manage hub projects, add or remove project entries."
90
+ to="/admin/projects"
91
+ icon={
92
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
93
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" />
94
+ <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
95
+ </svg>
96
+ }
97
+ />
98
+ <DashboardCard
99
+ title="Sections"
100
+ description="Organize documentation sections and navigation structure."
101
+ to="/admin/sections"
102
+ icon={
103
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
104
+ <line x1="8" y1="6" x2="21" y2="6" />
105
+ <line x1="8" y1="12" x2="21" y2="12" />
106
+ <line x1="8" y1="18" x2="21" y2="18" />
107
+ <line x1="3" y1="6" x2="3.01" y2="6" />
108
+ <line x1="3" y1="12" x2="3.01" y2="12" />
109
+ <line x1="3" y1="18" x2="3.01" y2="18" />
110
+ </svg>
111
+ }
112
+ />
113
+ <DashboardCard
114
+ title="Site Settings"
115
+ description="Configure site name, colors, links, and other settings."
116
+ to="/admin/settings"
117
+ icon={
118
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
119
+ <circle cx="12" cy="12" r="3" />
120
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
121
+ </svg>
122
+ }
123
+ />
124
+ </div>
125
+ </div>
126
+ );
127
+ }
128
+
129
+ export default AdminDashboard;