@geenius/docs 0.1.0 → 0.4.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.
Files changed (158) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +53 -1
  3. package/package.json +96 -13
  4. package/packages/convex/dist/index.d.ts +503 -0
  5. package/packages/convex/dist/index.js +482 -0
  6. package/packages/convex/dist/index.js.map +1 -0
  7. package/packages/react/dist/index.d.ts +439 -0
  8. package/packages/react/dist/index.js +4954 -0
  9. package/packages/react/dist/index.js.map +1 -0
  10. package/packages/react-css/{src/styles.css → dist/index.css} +183 -223
  11. package/packages/react-css/dist/index.css.map +1 -0
  12. package/packages/react-css/dist/index.d.ts +443 -0
  13. package/packages/react-css/dist/index.js +5058 -0
  14. package/packages/react-css/dist/index.js.map +1 -0
  15. package/packages/shared/dist/index.d.ts +684 -0
  16. package/packages/shared/dist/index.js +788 -0
  17. package/packages/shared/dist/index.js.map +1 -0
  18. package/packages/solidjs/dist/index.d.ts +435 -0
  19. package/packages/solidjs/dist/index.js +4584 -0
  20. package/packages/solidjs/dist/index.js.map +1 -0
  21. package/packages/solidjs-css/{src/styles.css → dist/index.css} +183 -223
  22. package/packages/solidjs-css/dist/index.css.map +1 -0
  23. package/packages/solidjs-css/dist/index.d.ts +432 -0
  24. package/packages/solidjs-css/dist/index.js +4934 -0
  25. package/packages/solidjs-css/dist/index.js.map +1 -0
  26. package/.changeset/config.json +0 -11
  27. package/.github/CODEOWNERS +0 -1
  28. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
  29. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
  30. package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
  31. package/.github/dependabot.yml +0 -11
  32. package/.github/workflows/ci.yml +0 -23
  33. package/.github/workflows/release.yml +0 -29
  34. package/.nvmrc +0 -1
  35. package/.project/ACCOUNT.yaml +0 -4
  36. package/.project/IDEAS.yaml +0 -7
  37. package/.project/PROJECT.yaml +0 -11
  38. package/.project/ROADMAP.yaml +0 -15
  39. package/CODE_OF_CONDUCT.md +0 -16
  40. package/CONTRIBUTING.md +0 -26
  41. package/SECURITY.md +0 -15
  42. package/SUPPORT.md +0 -8
  43. package/packages/convex/README.md +0 -1
  44. package/packages/convex/package.json +0 -12
  45. package/packages/convex/src/convex.config.ts +0 -3
  46. package/packages/convex/src/index.ts +0 -3
  47. package/packages/convex/src/mutations.ts +0 -270
  48. package/packages/convex/src/queries.ts +0 -175
  49. package/packages/convex/src/schema.ts +0 -55
  50. package/packages/react/README.md +0 -1
  51. package/packages/react/package.json +0 -36
  52. package/packages/react/src/DocsLayout.tsx +0 -116
  53. package/packages/react/src/DocsProvider.tsx +0 -93
  54. package/packages/react/src/RouterDocsContent.tsx +0 -148
  55. package/packages/react/src/RouterDocsLayout.tsx +0 -161
  56. package/packages/react/src/components/Breadcrumbs.tsx +0 -34
  57. package/packages/react/src/components/DocPage.tsx +0 -191
  58. package/packages/react/src/components/DocSearch.tsx +0 -140
  59. package/packages/react/src/components/DocSidebar.tsx +0 -86
  60. package/packages/react/src/components/DocsLayout.tsx +0 -62
  61. package/packages/react/src/components/EditButton.tsx +0 -26
  62. package/packages/react/src/components/PageNavigation.tsx +0 -45
  63. package/packages/react/src/components/TableOfContents.tsx +0 -46
  64. package/packages/react/src/components/VersionSelector.tsx +0 -60
  65. package/packages/react/src/components/index.ts +0 -9
  66. package/packages/react/src/hooks/index.ts +0 -8
  67. package/packages/react/src/hooks/useDocSearch.ts +0 -55
  68. package/packages/react/src/hooks/useDocs.ts +0 -57
  69. package/packages/react/src/hooks/useDocsAdmin.ts +0 -151
  70. package/packages/react/src/hooks/useTableOfContents.ts +0 -66
  71. package/packages/react/src/index.ts +0 -38
  72. package/packages/react/src/pages/DocSearchPage.tsx +0 -129
  73. package/packages/react/src/pages/DocViewPage.tsx +0 -158
  74. package/packages/react/src/pages/DocsAdminPage.tsx +0 -330
  75. package/packages/react/src/pages/DocsIndexPage.tsx +0 -172
  76. package/packages/react/src/pages/index.ts +0 -4
  77. package/packages/react/src/useDocs.ts +0 -58
  78. package/packages/react/tsup.config.ts +0 -12
  79. package/packages/react-css/README.md +0 -1
  80. package/packages/react-css/package.json +0 -37
  81. package/packages/react-css/src/DocsLayout.tsx +0 -117
  82. package/packages/react-css/src/DocsProvider.tsx +0 -93
  83. package/packages/react-css/src/RouterDocsContent.tsx +0 -60
  84. package/packages/react-css/src/RouterDocsLayout.tsx +0 -101
  85. package/packages/react-css/src/components/DocPage.tsx +0 -21
  86. package/packages/react-css/src/components/DocSearch.tsx +0 -55
  87. package/packages/react-css/src/components/DocSidebar.tsx +0 -56
  88. package/packages/react-css/src/components/DocsLayout.tsx +0 -28
  89. package/packages/react-css/src/components/common.tsx +0 -93
  90. package/packages/react-css/src/components/index.ts +0 -5
  91. package/packages/react-css/src/hooks/index.ts +0 -2
  92. package/packages/react-css/src/index.ts +0 -6
  93. package/packages/react-css/src/index.tsx +0 -3
  94. package/packages/react-css/src/pages/DocViewPage.tsx +0 -78
  95. package/packages/react-css/src/pages/DocsAdminPage.tsx +0 -101
  96. package/packages/react-css/src/pages/DocsIndexPage.tsx +0 -68
  97. package/packages/react-css/src/pages/index.ts +0 -3
  98. package/packages/react-css/src/useDocs.ts +0 -58
  99. package/packages/react-css/tsconfig.json +0 -19
  100. package/packages/react-css/tsup.config.ts +0 -10
  101. package/packages/shared/README.md +0 -1
  102. package/packages/shared/package.json +0 -31
  103. package/packages/shared/src/__tests__/docs.test.ts +0 -69
  104. package/packages/shared/src/config.ts +0 -80
  105. package/packages/shared/src/index.ts +0 -179
  106. package/packages/shared/src/providers/astro.ts +0 -94
  107. package/packages/shared/src/providers/fumadocs.ts +0 -116
  108. package/packages/shared/src/providers/internal.ts +0 -80
  109. package/packages/shared/src/types.ts +0 -73
  110. package/packages/shared/tsconfig.json +0 -18
  111. package/packages/shared/tsup.config.ts +0 -12
  112. package/packages/shared/vitest.config.ts +0 -4
  113. package/packages/solidjs/README.md +0 -1
  114. package/packages/solidjs/package.json +0 -33
  115. package/packages/solidjs/src/DocsLayout.tsx +0 -87
  116. package/packages/solidjs/src/DocsProvider.tsx +0 -95
  117. package/packages/solidjs/src/RouterDocsContent.tsx +0 -147
  118. package/packages/solidjs/src/RouterDocsLayout.tsx +0 -161
  119. package/packages/solidjs/src/components/Breadcrumbs.tsx +0 -27
  120. package/packages/solidjs/src/components/DocPage.tsx +0 -110
  121. package/packages/solidjs/src/components/DocSearch.tsx +0 -81
  122. package/packages/solidjs/src/components/DocSidebar.tsx +0 -92
  123. package/packages/solidjs/src/components/DocsLayout.tsx +0 -38
  124. package/packages/solidjs/src/components/EditButton.tsx +0 -15
  125. package/packages/solidjs/src/components/PageNavigation.tsx +0 -31
  126. package/packages/solidjs/src/components/TableOfContents.tsx +0 -41
  127. package/packages/solidjs/src/components/VersionSelector.tsx +0 -30
  128. package/packages/solidjs/src/components/index.ts +0 -9
  129. package/packages/solidjs/src/createDocs.ts +0 -62
  130. package/packages/solidjs/src/index.ts +0 -28
  131. package/packages/solidjs/src/pages/DocSearchPage.tsx +0 -72
  132. package/packages/solidjs/src/pages/DocViewPage.tsx +0 -80
  133. package/packages/solidjs/src/pages/DocsAdminPage.tsx +0 -123
  134. package/packages/solidjs/src/pages/DocsIndexPage.tsx +0 -85
  135. package/packages/solidjs/src/pages/index.ts +0 -4
  136. package/packages/solidjs/src/primitives/createDocSearch.ts +0 -42
  137. package/packages/solidjs/src/primitives/createDocs.ts +0 -35
  138. package/packages/solidjs/src/primitives/createDocsAdmin.ts +0 -63
  139. package/packages/solidjs/src/primitives/createTableOfContents.ts +0 -51
  140. package/packages/solidjs/src/primitives/index.ts +0 -4
  141. package/packages/solidjs/tsup.config.ts +0 -12
  142. package/packages/solidjs-css/README.md +0 -1
  143. package/packages/solidjs-css/package.json +0 -36
  144. package/packages/solidjs-css/src/DocsLayout.tsx +0 -106
  145. package/packages/solidjs-css/src/DocsProvider.tsx +0 -95
  146. package/packages/solidjs-css/src/RouterDocsContent.tsx +0 -54
  147. package/packages/solidjs-css/src/RouterDocsLayout.tsx +0 -104
  148. package/packages/solidjs-css/src/createDocs.ts +0 -62
  149. package/packages/solidjs-css/src/index.ts +0 -7
  150. package/packages/solidjs-css/src/index.tsx +0 -17
  151. package/packages/solidjs-css/src/pages/DocViewPage.tsx +0 -111
  152. package/packages/solidjs-css/src/pages/DocsAdminPage.tsx +0 -332
  153. package/packages/solidjs-css/src/pages/DocsIndexPage.tsx +0 -116
  154. package/packages/solidjs-css/src/pages/index.ts +0 -3
  155. package/packages/solidjs-css/src/primitives/index.ts +0 -1
  156. package/packages/solidjs-css/tsconfig.json +0 -20
  157. package/packages/solidjs-css/tsup.config.ts +0 -10
  158. package/pnpm-workspace.yaml +0 -2
@@ -1,129 +0,0 @@
1
- import React, { useCallback, useMemo } from 'react'
2
- import type { DocPage, DocSection, SearchResult } from '@geenius-docs/shared'
3
- import { buildDocsIndex, searchDocs, highlightMatch } from '@geenius-docs/shared'
4
- import { useDocSearch } from '../hooks/useDocSearch'
5
-
6
- interface DocSearchPageProps {
7
- tree: (DocSection & { pages: DocPage[]; pageCount: number })[] | undefined
8
- onSelectPage?: (page: DocPage, section: DocSection) => void
9
- }
10
-
11
- export function DocSearchPage({ tree, onSelectPage }: DocSearchPageProps) {
12
- const sections = useMemo(() => tree ?? [], [tree])
13
- const flatPages = useMemo(() => sections.flatMap((s) => s.pages ?? []), [sections])
14
-
15
- const searchFn = useCallback(
16
- (q: string): SearchResult[] => {
17
- const index = buildDocsIndex(flatPages, sections)
18
- return searchDocs(q, index)
19
- },
20
- [flatPages, sections]
21
- )
22
-
23
- const { results, isSearching, query, setQuery } = useDocSearch(searchFn)
24
-
25
- // Loading
26
- if (tree === undefined) {
27
- return (
28
- <div className="min-h-screen bg-[#090a0f] px-6 py-16">
29
- <div className="mx-auto max-w-2xl">
30
- <div className="mb-8 h-12 animate-pulse rounded-xl bg-white/5" />
31
- <div className="space-y-3">
32
- {Array.from({ length: 5 }).map((_, i) => (
33
- <div key={i} className="h-20 animate-pulse rounded-xl bg-white/5" />
34
- ))}
35
- </div>
36
- </div>
37
- </div>
38
- )
39
- }
40
-
41
- return (
42
- <div className="min-h-screen bg-[#090a0f] text-white">
43
- <div className="mx-auto max-w-2xl px-6 py-16">
44
- <h1 className="mb-8 text-2xl font-bold">Search Documentation</h1>
45
-
46
- {/* Search input */}
47
- <div className="relative mb-8">
48
- <svg className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 text-white/30" viewBox="0 0 20 20" fill="currentColor">
49
- <path fillRule="evenorid" d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z" clipRule="evenodd" />
50
- </svg>
51
- <input
52
- type="text"
53
- value={query}
54
- onChange={(e) => setQuery(e.target.value)}
55
- placeholder="Type to search…"
56
- autoFocus
57
- className="w-full rounded-xl border border-white/10 bg-white/5 py-3.5 pl-12 pr-4 text-sm text-white placeholder-white/30 outline-none transition-colors focus:border-indigo-500/40 focus:ring-1 focus:ring-indigo-500/20"
58
- />
59
- {isSearching && (
60
- <div className="absolute right-4 top-1/2 -translate-y-1/2">
61
- <div className="h-4 w-4 animate-spin rounded-full border-2 border-white/10 border-t-indigo-400" />
62
- </div>
63
- )}
64
- </div>
65
-
66
- {/* Results */}
67
- {results.length > 0 && (
68
- <div className="space-y-2">
69
- {results.map((result) => {
70
- const section = sections.find((s) => s.slug === result.sectionSlug)
71
- const page = flatPages.find((p) => p.id === result.pageId)
72
- return (
73
- <button
74
- key={result.pageId}
75
- type="button"
76
- onClick={() => {
77
- if (page && section && onSelectPage) onSelectPage(page, section)
78
- }}
79
- className="flex w-full flex-col gap-1.5 rounded-xl border border-white/5 bg-white/[0.02] p-4 text-left transition-all hover:border-indigo-500/20 hover:bg-white/[0.05]"
80
- >
81
- <div className="flex items-center gap-2">
82
- <span className="rounded bg-indigo-500/20 px-2 py-0.5 text-[10px] font-medium text-indigo-300">
83
- {result.sectionTitle}
84
- </span>
85
- <span className="text-sm font-medium text-white/90">{result.pageTitle}</span>
86
- <span className="ml-auto text-xs tabular-nums text-white/20">
87
- {page?.readingTime ?? 0} min
88
- </span>
89
- </div>
90
- <p className="text-xs leading-relaxed text-white/40">
91
- {highlightMatch(result.highlight, query)}
92
- </p>
93
- {result.tags.length > 0 && (
94
- <div className="flex gap-1 mt-1">
95
- {result.tags.slice(0, 4).map((tag) => (
96
- <span key={tag} className="rounded bg-white/5 px-1.5 py-0.5 text-[10px] text-white/30">
97
- {tag}
98
- </span>
99
- ))}
100
- </div>
101
- )}
102
- </button>
103
- )
104
- })}
105
- </div>
106
- )}
107
-
108
- {/* Empty state */}
109
- {query.trim() && results.length === 0 && !isSearching && (
110
- <div className="flex flex-col items-center py-16 text-center">
111
- <div className="mb-4 text-5xl opacity-30">🔍</div>
112
- <h3 className="mb-2 text-lg font-medium text-white/60">No results found</h3>
113
- <p className="text-sm text-white/30">
114
- Try different keywords or check your spelling.
115
- </p>
116
- </div>
117
- )}
118
-
119
- {/* Initial state */}
120
- {!query.trim() && (
121
- <div className="flex flex-col items-center py-16 text-center">
122
- <div className="mb-4 text-5xl opacity-20">📖</div>
123
- <p className="text-sm text-white/30">Start typing to search across all documentation</p>
124
- </div>
125
- )}
126
- </div>
127
- </div>
128
- )
129
- }
@@ -1,158 +0,0 @@
1
- import React, { useEffect, useMemo } from 'react'
2
- import type { DocPage as DocPageType, DocSection } from '@geenius-docs/shared'
3
- import { buildBreadcrumbs } from '@geenius-docs/shared'
4
- import { useDocs } from '../hooks/useDocs'
5
- import { useTableOfContents } from '../hooks/useTableOfContents'
6
- import { DocsLayout } from '../components/DocsLayout'
7
- import { DocPage } from '../components/DocPage'
8
- import { EditButton } from '../components/EditButton'
9
- import { PageNavigation } from '../components/PageNavigation'
10
-
11
- interface DocViewPageProps {
12
- tree: (DocSection & { pages: DocPageType[]; pageCount: number })[] | undefined
13
- page: DocPageType | null | undefined
14
- editPageUrl?: string
15
- onNavigate: (page: DocPageType, section: DocSection) => void
16
- onIncrementView?: (pageId: string) => void
17
- }
18
-
19
- export function DocViewPage({
20
- tree,
21
- page,
22
- editPageUrl,
23
- onNavigate,
24
- onIncrementView,
25
- }: DocViewPageProps) {
26
- const docs = useDocs(tree)
27
- const { toc, activeId } = useTableOfContents(page?.content)
28
-
29
- // Increment view count on mount
30
- useEffect(() => {
31
- if (page?.id && onIncrementView) {
32
- onIncrementView(page.id)
33
- }
34
- }, [page?.id, onIncrementView])
35
-
36
- // Build breadcrumbs
37
- const breadcrumbs = useMemo(() => {
38
- if (!page) return []
39
- return buildBreadcrumbs(docs.sections, page.sectionId, page.slug)
40
- }, [docs.sections, page])
41
-
42
- // Find prev/next pages
43
- const { prev, next } = useMemo(() => {
44
- if (!page) return { prev: undefined, next: undefined }
45
- const allPages = docs.flatPages
46
- const idx = allPages.findIndex((p) => p.id === page.id)
47
- const section = docs.sections.find((s) => s.id === page.sectionId)
48
- return {
49
- prev: idx > 0
50
- ? { title: allPages[idx - 1].title, href: `/docs/${section?.slug ?? ''}/${allPages[idx - 1].slug}` }
51
- : undefined,
52
- next: idx < allPages.length - 1
53
- ? { title: allPages[idx + 1].title, href: `/docs/${section?.slug ?? ''}/${allPages[idx + 1].slug}` }
54
- : undefined,
55
- }
56
- }, [page, docs.flatPages, docs.sections])
57
-
58
- // Loading
59
- if (docs.isLoading || page === undefined) {
60
- return (
61
- <div className="flex min-h-screen bg-[#090a0f]">
62
- <div className="hidden w-[260px] shrink-0 border-r border-white/5 bg-[#0b0c12] lg:block">
63
- <div className="space-y-3 p-4">
64
- {Array.from({ length: 8 }).map((_, i) => (
65
- <div key={i} className="h-8 animate-pulse rounded-lg bg-white/5" />
66
- ))}
67
- </div>
68
- </div>
69
- <div className="flex-1 px-10 py-8">
70
- <div className="mx-auto max-w-3xl space-y-4">
71
- <div className="h-5 w-48 animate-pulse rounded bg-white/5" />
72
- <div className="h-10 w-96 animate-pulse rounded bg-white/5" />
73
- <div className="mt-8 space-y-3">
74
- {Array.from({ length: 12 }).map((_, i) => (
75
- <div key={i} className="h-4 animate-pulse rounded bg-white/5" style={{ width: `${60 + Math.random() * 40}%` }} />
76
- ))}
77
- </div>
78
- </div>
79
- </div>
80
- </div>
81
- )
82
- }
83
-
84
- // Not found
85
- if (!page) {
86
- return (
87
- <div className="flex min-h-screen flex-col items-center justify-center bg-[#090a0f] text-center">
88
- <div className="mb-4 text-6xl opacity-30">🔍</div>
89
- <h2 className="mb-2 text-xl font-semibold text-white/80">Page not found</h2>
90
- <p className="text-sm text-white/40">The documentation page you&apos;re looking for doesn&apos;t exist.</p>
91
- </div>
92
- )
93
- }
94
-
95
- return (
96
- <DocsLayout
97
- sections={docs.sections}
98
- currentPage={page}
99
- toc={toc}
100
- activeHeadingId={activeId}
101
- breadcrumbs={breadcrumbs}
102
- currentPageId={page.id}
103
- onNavigate={onNavigate}
104
- >
105
- {/* Page header */}
106
- <div className="mb-8">
107
- <h1 className="mb-3 text-3xl font-bold tracking-tight">{page.title}</h1>
108
- <div className="flex flex-wrap items-center gap-4 text-sm text-white/40">
109
- <span className="flex items-center gap-1.5">
110
- {page.author.avatar ? (
111
- <img src={page.author.avatar} alt="" className="h-5 w-5 rounded-full" />
112
- ) : (
113
- <span className="flex h-5 w-5 items-center justify-center rounded-full bg-indigo-500/20 text-[10px] text-indigo-300">
114
- {page.author.name[0]}
115
- </span>
116
- )}
117
- {page.author.name}
118
- </span>
119
- {page.readingTime > 0 && (
120
- <span>{page.readingTime} min read</span>
121
- )}
122
- {page.lastEditedBy && (
123
- <span>
124
- Edited by {page.lastEditedBy.name}
125
- </span>
126
- )}
127
- {page.viewCount > 0 && (
128
- <span>{page.viewCount.toLocaleString()} views</span>
129
- )}
130
- </div>
131
-
132
- {/* Tags */}
133
- {page.tags.length > 0 && (
134
- <div className="mt-3 flex flex-wrap gap-1.5">
135
- {page.tags.map((tag) => (
136
- <span key={tag} className="rounded-full bg-white/5 px-2.5 py-0.5 text-xs text-white/40">
137
- {tag}
138
- </span>
139
- ))}
140
- </div>
141
- )}
142
- </div>
143
-
144
- {/* Content */}
145
- <DocPage page={page} />
146
-
147
- {/* Footer */}
148
- <div className="mt-10 flex items-center justify-between border-t border-white/5 pt-6">
149
- <EditButton pageSlug={page.slug} editUrl={editPageUrl} />
150
- {page.version && (
151
- <span className="text-xs text-white/25">v{page.version}</span>
152
- )}
153
- </div>
154
-
155
- <PageNavigation prev={prev} next={next} />
156
- </DocsLayout>
157
- )
158
- }
@@ -1,330 +0,0 @@
1
- import React, { useState, useMemo } from 'react'
2
- import type { DocPage, DocSection } from '@geenius-docs/shared'
3
- import type { DocsAdminActions } from '../hooks/useDocsAdmin'
4
-
5
- interface DocsAdminPageProps {
6
- tree: (DocSection & { pages: DocPage[]; pageCount: number })[] | undefined
7
- allPages?: DocPage[]
8
- admin: DocsAdminActions
9
- }
10
-
11
- export function DocsAdminPage({ tree, allPages, admin }: DocsAdminPageProps) {
12
- const sections = useMemo(() => tree ?? [], [tree])
13
- const [selectedSectionId, setSelectedSectionId] = useState<string | null>(null)
14
- const [sectionForm, setSectionForm] = useState({ title: '', slug: '', description: '', icon: '', access: 'team' as const })
15
- const [pageForm, setPageForm] = useState({ title: '', slug: '', content: '', access: 'team' as const, tags: '' })
16
- const [showSectionForm, setShowSectionForm] = useState(false)
17
- const [showPageForm, setShowPageForm] = useState(false)
18
-
19
- const selectedSection = sections.find((s) => s.id === selectedSectionId)
20
- const sectionPages = useMemo(() => {
21
- if (!selectedSection) return []
22
- if (allPages) return allPages.filter((p) => p.sectionId === selectedSectionId)
23
- return selectedSection.pages ?? []
24
- }, [selectedSection, allPages, selectedSectionId])
25
-
26
- // Loading
27
- if (tree === undefined) {
28
- return (
29
- <div className="min-h-screen bg-[#090a0f] px-6 py-12">
30
- <div className="mx-auto max-w-6xl">
31
- <div className="mb-8 h-10 w-48 animate-pulse rounded bg-white/5" />
32
- <div className="grid gap-6 md:grid-cols-2">
33
- <div className="h-96 animate-pulse rounded-xl bg-white/5" />
34
- <div className="h-96 animate-pulse rounded-xl bg-white/5" />
35
- </div>
36
- </div>
37
- </div>
38
- )
39
- }
40
-
41
- return (
42
- <div className="min-h-screen bg-[#090a0f] text-white">
43
- <div className="mx-auto max-w-6xl px-6 py-12">
44
- <h1 className="mb-8 text-2xl font-bold">Docs Admin</h1>
45
-
46
- <div className="grid gap-6 md:grid-cols-2">
47
- {/* Left panel — Sections */}
48
- <div className="rounded-xl border border-white/8 bg-white/[0.02] p-5">
49
- <div className="mb-4 flex items-center justify-between">
50
- <h2 className="text-lg font-semibold text-white/80">Sections</h2>
51
- <button
52
- type="button"
53
- onClick={() => setShowSectionForm(!showSectionForm)}
54
- className="rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-medium transition-colors hover:bg-indigo-500"
55
- >
56
- + Add Section
57
- </button>
58
- </div>
59
-
60
- {/* Section form */}
61
- {showSectionForm && (
62
- <div className="mb-4 space-y-2 rounded-lg border border-white/10 bg-white/5 p-4">
63
- <input
64
- type="text"
65
- placeholder="Title"
66
- value={sectionForm.title}
67
- onChange={(e) => setSectionForm({ ...sectionForm, title: e.target.value })}
68
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none focus:border-indigo-500/40"
69
- />
70
- <input
71
- type="text"
72
- placeholder="Slug"
73
- value={sectionForm.slug}
74
- onChange={(e) => setSectionForm({ ...sectionForm, slug: e.target.value })}
75
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none focus:border-indigo-500/40"
76
- />
77
- <input
78
- type="text"
79
- placeholder="Icon (emoji)"
80
- value={sectionForm.icon}
81
- onChange={(e) => setSectionForm({ ...sectionForm, icon: e.target.value })}
82
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none focus:border-indigo-500/40"
83
- />
84
- <input
85
- type="text"
86
- placeholder="Description"
87
- value={sectionForm.description}
88
- onChange={(e) => setSectionForm({ ...sectionForm, description: e.target.value })}
89
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none focus:border-indigo-500/40"
90
- />
91
- <select
92
- value={sectionForm.access}
93
- onChange={(e) => setSectionForm({ ...sectionForm, access: e.target.value as 'public' | 'team' | 'admin' })}
94
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none"
95
- >
96
- <option value="public">Public</option>
97
- <option value="team">Team</option>
98
- <option value="admin">Admin</option>
99
- </select>
100
- <div className="flex gap-2">
101
- <button
102
- type="button"
103
- onClick={async () => {
104
- await admin.createSection({
105
- ...sectionForm,
106
- order: sections.length,
107
- icon: sectionForm.icon || undefined,
108
- description: sectionForm.description || undefined,
109
- })
110
- setSectionForm({ title: '', slug: '', description: '', icon: '', access: 'team' })
111
- setShowSectionForm(false)
112
- }}
113
- className="rounded-lg bg-indigo-600 px-4 py-2 text-xs font-medium hover:bg-indigo-500"
114
- >
115
- Create
116
- </button>
117
- <button
118
- type="button"
119
- onClick={() => setShowSectionForm(false)}
120
- className="rounded-lg border border-white/10 px-4 py-2 text-xs hover:bg-white/5"
121
- >
122
- Cancel
123
- </button>
124
- </div>
125
- </div>
126
- )}
127
-
128
- {/* Sections list */}
129
- <div className="space-y-1">
130
- {sections.length === 0 && (
131
- <p className="py-8 text-center text-sm text-white/30">No sections yet</p>
132
- )}
133
- {sections.map((section) => (
134
- <div
135
- key={section.id}
136
- className={`group flex items-center gap-2 rounded-lg px-3 py-2.5 transition-colors cursor-pointer ${
137
- selectedSectionId === section.id
138
- ? 'bg-indigo-500/15 text-indigo-300'
139
- : 'hover:bg-white/5 text-white/70'
140
- }`}
141
- onClick={() => setSelectedSectionId(section.id)}
142
- >
143
- {section.icon && <span>{section.icon}</span>}
144
- <span className="flex-1 text-sm font-medium truncate">{section.title}</span>
145
- <span className="text-[11px] tabular-nums opacity-40">{section.pageCount ?? 0}</span>
146
- <button
147
- type="button"
148
- onClick={(e) => {
149
- e.stopPropagation()
150
- if (confirm(`Delete section "${section.title}"?`)) {
151
- admin.deleteSection(section.id)
152
- }
153
- }}
154
- className="rounded p-1 text-white/20 opacity-0 transition-opacity group-hover:opacity-100 hover:text-red-400"
155
- title="Delete section"
156
- >
157
- <svg className="h-3.5 w-3.5" viewBox="0 0 16 16" fill="currentColor">
158
- <path d="M5.5 5.5A.5.5 0 016 6v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm2.5 0a.5.5 0 01.5.5v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm3 .5a.5.5 0 00-1 0v6a.5.5 0 001 0V6z" />
159
- <path fillRule="evenodd" d="M14.5 3a1 1 0 01-1 1H13v9a2 2 0 01-2 2H5a2 2 0 01-2-2V4h-.5a1 1 0 010-2H6a1 1 0 011-1h2a1 1 0 011 1h3.5a1 1 0 011 1zM4.118 4L4 4.059V13a1 1 0 001 1h6a1 1 0 001-1V4.059L11.882 4H4.118z" clipRule="evenodd" />
160
- </svg>
161
- </button>
162
- </div>
163
- ))}
164
- </div>
165
- </div>
166
-
167
- {/* Right panel — Pages */}
168
- <div className="rounded-xl border border-white/8 bg-white/[0.02] p-5">
169
- <div className="mb-4 flex items-center justify-between">
170
- <h2 className="text-lg font-semibold text-white/80">
171
- {selectedSection ? `Pages — ${selectedSection.title}` : 'Pages'}
172
- </h2>
173
- {selectedSection && (
174
- <button
175
- type="button"
176
- onClick={() => setShowPageForm(!showPageForm)}
177
- className="rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-medium transition-colors hover:bg-indigo-500"
178
- >
179
- + Add Page
180
- </button>
181
- )}
182
- </div>
183
-
184
- {!selectedSection && (
185
- <p className="py-16 text-center text-sm text-white/30">Select a section to manage pages</p>
186
- )}
187
-
188
- {/* Page form */}
189
- {showPageForm && selectedSection && (
190
- <div className="mb-4 space-y-2 rounded-lg border border-white/10 bg-white/5 p-4">
191
- <input
192
- type="text"
193
- placeholder="Title"
194
- value={pageForm.title}
195
- onChange={(e) => setPageForm({ ...pageForm, title: e.target.value })}
196
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none focus:border-indigo-500/40"
197
- />
198
- <input
199
- type="text"
200
- placeholder="Slug"
201
- value={pageForm.slug}
202
- onChange={(e) => setPageForm({ ...pageForm, slug: e.target.value })}
203
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none focus:border-indigo-500/40"
204
- />
205
- <textarea
206
- placeholder="Content (MDX)"
207
- value={pageForm.content}
208
- onChange={(e) => setPageForm({ ...pageForm, content: e.target.value })}
209
- rows={5}
210
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none focus:border-indigo-500/40 resize-y"
211
- />
212
- <input
213
- type="text"
214
- placeholder="Tags (comma separated)"
215
- value={pageForm.tags}
216
- onChange={(e) => setPageForm({ ...pageForm, tags: e.target.value })}
217
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none focus:border-indigo-500/40"
218
- />
219
- <select
220
- value={pageForm.access}
221
- onChange={(e) => setPageForm({ ...pageForm, access: e.target.value as 'public' | 'team' | 'admin' })}
222
- className="w-full rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-sm outline-none"
223
- >
224
- <option value="public">Public</option>
225
- <option value="team">Team</option>
226
- <option value="admin">Admin</option>
227
- </select>
228
- <div className="flex gap-2">
229
- <button
230
- type="button"
231
- onClick={async () => {
232
- await admin.createPage({
233
- title: pageForm.title,
234
- slug: pageForm.slug,
235
- content: pageForm.content,
236
- sectionId: selectedSection.id,
237
- access: pageForm.access,
238
- tags: pageForm.tags ? pageForm.tags.split(',').map((t) => t.trim()) : [],
239
- order: sectionPages.length,
240
- })
241
- setPageForm({ title: '', slug: '', content: '', access: 'team', tags: '' })
242
- setShowPageForm(false)
243
- }}
244
- className="rounded-lg bg-indigo-600 px-4 py-2 text-xs font-medium hover:bg-indigo-500"
245
- >
246
- Create
247
- </button>
248
- <button
249
- type="button"
250
- onClick={() => setShowPageForm(false)}
251
- className="rounded-lg border border-white/10 px-4 py-2 text-xs hover:bg-white/5"
252
- >
253
- Cancel
254
- </button>
255
- </div>
256
- </div>
257
- )}
258
-
259
- {/* Pages table */}
260
- {selectedSection && sectionPages.length > 0 && (
261
- <div className="space-y-1">
262
- {sectionPages.map((page) => (
263
- <div
264
- key={page.id}
265
- className="group flex items-center gap-3 rounded-lg px-3 py-2.5 transition-colors hover:bg-white/5"
266
- >
267
- <div className="flex-1 min-w-0">
268
- <p className="text-sm font-medium text-white/80 truncate">{page.title}</p>
269
- <p className="text-[11px] text-white/30">/{page.slug}</p>
270
- </div>
271
- <span
272
- className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-medium ${
273
- page.status === 'published'
274
- ? 'bg-emerald-500/15 text-emerald-400'
275
- : page.status === 'archived'
276
- ? 'bg-white/5 text-white/30'
277
- : 'bg-amber-500/15 text-amber-400'
278
- }`}
279
- >
280
- {page.status}
281
- </span>
282
- <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
283
- {page.status === 'draft' && (
284
- <button
285
- type="button"
286
- onClick={() => admin.publishPage(page.id)}
287
- className="rounded px-2 py-1 text-[11px] text-emerald-400 hover:bg-emerald-500/10"
288
- title="Publish"
289
- >
290
- Publish
291
- </button>
292
- )}
293
- {page.status === 'published' && (
294
- <button
295
- type="button"
296
- onClick={() => admin.archivePage(page.id)}
297
- className="rounded px-2 py-1 text-[11px] text-amber-400 hover:bg-amber-500/10"
298
- title="Archive"
299
- >
300
- Archive
301
- </button>
302
- )}
303
- <button
304
- type="button"
305
- onClick={() => {
306
- if (confirm(`Delete page "${page.title}"?`)) admin.deletePage(page.id)
307
- }}
308
- className="rounded p-1 text-white/20 hover:text-red-400"
309
- title="Delete"
310
- >
311
- <svg className="h-3.5 w-3.5" viewBox="0 0 16 16" fill="currentColor">
312
- <path d="M5.5 5.5A.5.5 0 016 6v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm2.5 0a.5.5 0 01.5.5v6a.5.5 0 01-1 0V6a.5.5 0 01.5-.5zm3 .5a.5.5 0 00-1 0v6a.5.5 0 001 0V6z" />
313
- <path fillRule="evenodd" d="M14.5 3a1 1 0 01-1 1H13v9a2 2 0 01-2 2H5a2 2 0 01-2-2V4h-.5a1 1 0 010-2H6a1 1 0 011-1h2a1 1 0 011 1h3.5a1 1 0 011 1zM4.118 4L4 4.059V13a1 1 0 001 1h6a1 1 0 001-1V4.059L11.882 4H4.118z" clipRule="evenodd" />
314
- </svg>
315
- </button>
316
- </div>
317
- </div>
318
- ))}
319
- </div>
320
- )}
321
-
322
- {selectedSection && sectionPages.length === 0 && !showPageForm && (
323
- <p className="py-12 text-center text-sm text-white/30">No pages in this section</p>
324
- )}
325
- </div>
326
- </div>
327
- </div>
328
- </div>
329
- )
330
- }