@notionx/create-notionx-app 1.0.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 (239) hide show
  1. package/README.md +139 -0
  2. package/dist/answers.js +332 -0
  3. package/dist/answers.js.map +1 -0
  4. package/dist/cli-notionx.js +388 -0
  5. package/dist/cli-notionx.js.map +1 -0
  6. package/dist/cli-notionx.test.js +277 -0
  7. package/dist/cli-notionx.test.js.map +1 -0
  8. package/dist/diff.js +40 -0
  9. package/dist/diff.js.map +1 -0
  10. package/dist/diff.test.js +90 -0
  11. package/dist/diff.test.js.map +1 -0
  12. package/dist/index.js +99 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/locale-add/apply.js +39 -0
  15. package/dist/locale-add/apply.js.map +1 -0
  16. package/dist/locale-add/format.js +38 -0
  17. package/dist/locale-add/format.js.map +1 -0
  18. package/dist/locale-add/list.js +44 -0
  19. package/dist/locale-add/list.js.map +1 -0
  20. package/dist/locale-add/list.test.js +45 -0
  21. package/dist/locale-add/list.test.js.map +1 -0
  22. package/dist/locale-add/plan.js +128 -0
  23. package/dist/locale-add/plan.js.map +1 -0
  24. package/dist/locale-add/validate.js +46 -0
  25. package/dist/locale-add/validate.js.map +1 -0
  26. package/dist/metadata.js +41 -0
  27. package/dist/metadata.js.map +1 -0
  28. package/dist/notion-translation-sources/apply.js +61 -0
  29. package/dist/notion-translation-sources/apply.js.map +1 -0
  30. package/dist/notion-translation-sources/index.js +3 -0
  31. package/dist/notion-translation-sources/index.js.map +1 -0
  32. package/dist/notion-translation-sources/plan.js +33 -0
  33. package/dist/notion-translation-sources/plan.js.map +1 -0
  34. package/dist/notionx-source.js +142 -0
  35. package/dist/notionx-source.js.map +1 -0
  36. package/dist/notionx-source.test.js +144 -0
  37. package/dist/notionx-source.test.js.map +1 -0
  38. package/dist/password.js +18 -0
  39. package/dist/password.js.map +1 -0
  40. package/dist/presets.js +83 -0
  41. package/dist/presets.js.map +1 -0
  42. package/dist/presets.test.js +50 -0
  43. package/dist/presets.test.js.map +1 -0
  44. package/dist/prompt.js +218 -0
  45. package/dist/prompt.js.map +1 -0
  46. package/dist/provision/cloudflare.js +236 -0
  47. package/dist/provision/cloudflare.js.map +1 -0
  48. package/dist/provision/dependencies.js +219 -0
  49. package/dist/provision/dependencies.js.map +1 -0
  50. package/dist/provision/index.js +681 -0
  51. package/dist/provision/index.js.map +1 -0
  52. package/dist/provision/index.test.js +54 -0
  53. package/dist/provision/index.test.js.map +1 -0
  54. package/dist/provision/inspect.js +109 -0
  55. package/dist/provision/inspect.js.map +1 -0
  56. package/dist/provision/inspect.test.js +75 -0
  57. package/dist/provision/inspect.test.js.map +1 -0
  58. package/dist/provision/notion.js +1981 -0
  59. package/dist/provision/notion.js.map +1 -0
  60. package/dist/provision/notion.test.js +542 -0
  61. package/dist/provision/notion.test.js.map +1 -0
  62. package/dist/provision/ntn-credentials.js +198 -0
  63. package/dist/provision/ntn-credentials.js.map +1 -0
  64. package/dist/provision/options.js +15 -0
  65. package/dist/provision/options.js.map +1 -0
  66. package/dist/provision/password-hash.js +78 -0
  67. package/dist/provision/password-hash.js.map +1 -0
  68. package/dist/provision/prompts.js +115 -0
  69. package/dist/provision/prompts.js.map +1 -0
  70. package/dist/provision/repair.js +48 -0
  71. package/dist/provision/repair.js.map +1 -0
  72. package/dist/provision/repair.test.js +141 -0
  73. package/dist/provision/repair.test.js.map +1 -0
  74. package/dist/provision/shell.js +84 -0
  75. package/dist/provision/shell.js.map +1 -0
  76. package/dist/provision/wire.js +78 -0
  77. package/dist/provision/wire.js.map +1 -0
  78. package/dist/registry/doctor.js +181 -0
  79. package/dist/registry/doctor.js.map +1 -0
  80. package/dist/registry/doctor.test.js +180 -0
  81. package/dist/registry/doctor.test.js.map +1 -0
  82. package/dist/registry/install.js +217 -0
  83. package/dist/registry/install.js.map +1 -0
  84. package/dist/registry/install.test.js +168 -0
  85. package/dist/registry/install.test.js.map +1 -0
  86. package/dist/registry/load-registry.js +24 -0
  87. package/dist/registry/load-registry.js.map +1 -0
  88. package/dist/registry/load-registry.test.js +59 -0
  89. package/dist/registry/load-registry.test.js.map +1 -0
  90. package/dist/registry/migration-planner.js +204 -0
  91. package/dist/registry/migration-planner.js.map +1 -0
  92. package/dist/registry/migration-planner.test.js +340 -0
  93. package/dist/registry/migration-planner.test.js.map +1 -0
  94. package/dist/registry/migrations-store.js +125 -0
  95. package/dist/registry/migrations-store.js.map +1 -0
  96. package/dist/registry/migrations-store.test.js +163 -0
  97. package/dist/registry/migrations-store.test.js.map +1 -0
  98. package/dist/registry/migrations-types.js +25 -0
  99. package/dist/registry/migrations-types.js.map +1 -0
  100. package/dist/registry/project-meta.js +84 -0
  101. package/dist/registry/project-meta.js.map +1 -0
  102. package/dist/registry/registry-items.js +354 -0
  103. package/dist/registry/registry-items.js.map +1 -0
  104. package/dist/registry/registry-items.test.js +99 -0
  105. package/dist/registry/registry-items.test.js.map +1 -0
  106. package/dist/registry/registry-store.js +232 -0
  107. package/dist/registry/registry-store.js.map +1 -0
  108. package/dist/registry/registry-store.test.js +136 -0
  109. package/dist/registry/registry-store.test.js.map +1 -0
  110. package/dist/registry/registry-types.js +18 -0
  111. package/dist/registry/registry-types.js.map +1 -0
  112. package/dist/registry/registry-types.test.js +146 -0
  113. package/dist/registry/registry-types.test.js.map +1 -0
  114. package/dist/registry/render-content-source-files.js +158 -0
  115. package/dist/registry/render-content-source-files.js.map +1 -0
  116. package/dist/registry/render-multi-source.js +296 -0
  117. package/dist/registry/render-multi-source.js.map +1 -0
  118. package/dist/registry/render-multi-source.test.js +110 -0
  119. package/dist/registry/render-multi-source.test.js.map +1 -0
  120. package/dist/registry/text-utils.js +42 -0
  121. package/dist/registry/text-utils.js.map +1 -0
  122. package/dist/registry/uninstall.js +250 -0
  123. package/dist/registry/uninstall.js.map +1 -0
  124. package/dist/registry/uninstall.test.js +264 -0
  125. package/dist/registry/uninstall.test.js.map +1 -0
  126. package/dist/registry/update.js +280 -0
  127. package/dist/registry/update.js.map +1 -0
  128. package/dist/registry/update.test.js +229 -0
  129. package/dist/registry/update.test.js.map +1 -0
  130. package/dist/render.js +549 -0
  131. package/dist/render.js.map +1 -0
  132. package/dist/render.test.js +414 -0
  133. package/dist/render.test.js.map +1 -0
  134. package/dist/templates/.dev.vars.example.tmpl +32 -0
  135. package/dist/templates/.gitignore.tmpl +58 -0
  136. package/dist/templates/README.md.tmpl +417 -0
  137. package/dist/templates/app/[slug]/page.tsx.tmpl +55 -0
  138. package/dist/templates/app/admin/account/page.tsx.tmpl +18 -0
  139. package/dist/templates/app/admin/content-models/page.tsx.tmpl +6 -0
  140. package/dist/templates/app/admin/layout.tsx.tmpl +90 -0
  141. package/dist/templates/app/admin/loading.tsx.tmpl +6 -0
  142. package/dist/templates/app/admin/page.tsx.tmpl +17 -0
  143. package/dist/templates/app/api/auth/google/callback/route.ts.tmpl +3 -0
  144. package/dist/templates/app/api/auth/google/route.ts.tmpl +3 -0
  145. package/dist/templates/app/api/auth/verify-email/route.ts.tmpl +3 -0
  146. package/dist/templates/app/api/auth/viewer/route.ts.tmpl +3 -0
  147. package/dist/templates/app/api/health/route.ts.tmpl +3 -0
  148. package/dist/templates/app/api/{{contentSourceId}}/[slug]/route.ts.tmpl +27 -0
  149. package/dist/templates/app/api/{{contentSourceId}}/route.ts.tmpl +18 -0
  150. package/dist/templates/app/globals.css.tmpl +109 -0
  151. package/dist/templates/app/layout.tsx.tmpl +56 -0
  152. package/dist/templates/app/login/page.tsx.tmpl +154 -0
  153. package/dist/templates/app/page.fallback.tsx.tmpl +31 -0
  154. package/dist/templates/app/page.tsx.tmpl +42 -0
  155. package/dist/templates/app/register/page.tsx.tmpl +138 -0
  156. package/dist/templates/app/{{contentSourceListPath}}/[slug]/page.tsx.tmpl +113 -0
  157. package/dist/templates/app/{{contentSourceListPath}}/page.tsx.tmpl +74 -0
  158. package/dist/templates/components/content/post-card.tsx.tmpl +80 -0
  159. package/dist/templates/components/notion-blocks.tsx.tmpl +668 -0
  160. package/dist/templates/components/page-blocks/feature-grid-block.tsx.tmpl +68 -0
  161. package/dist/templates/components/page-blocks/hero-block.tsx.tmpl +73 -0
  162. package/dist/templates/components/page-blocks/latest-posts-block.tsx.tmpl +59 -0
  163. package/dist/templates/components/page-blocks/story-block.tsx.tmpl +70 -0
  164. package/dist/templates/components/page-blocks.fallback.tsx.tmpl +17 -0
  165. package/dist/templates/components/page-blocks.tsx.tmpl +32 -0
  166. package/dist/templates/components/search/search-dialog.tsx.tmpl +171 -0
  167. package/dist/templates/components/site/locale-switcher.tsx.tmpl +65 -0
  168. package/dist/templates/components/site/site-footer.tsx.tmpl +106 -0
  169. package/dist/templates/components/site/site-header.tsx.tmpl +80 -0
  170. package/dist/templates/components/site/site-shell.tsx.tmpl +20 -0
  171. package/dist/templates/components/site/theme-bootstrap.tsx.tmpl +51 -0
  172. package/dist/templates/components/theme-provider.tsx.tmpl +14 -0
  173. package/dist/templates/components/theme-toggle.tsx.tmpl +38 -0
  174. package/dist/templates/components/ui/accordion.tsx.tmpl +56 -0
  175. package/dist/templates/components/ui/alert.tsx.tmpl +59 -0
  176. package/dist/templates/components/ui/aspect-ratio.tsx.tmpl +8 -0
  177. package/dist/templates/components/ui/avatar.tsx.tmpl +44 -0
  178. package/dist/templates/components/ui/badge.tsx.tmpl +33 -0
  179. package/dist/templates/components/ui/button.tsx.tmpl +56 -0
  180. package/dist/templates/components/ui/card.tsx.tmpl +61 -0
  181. package/dist/templates/components/ui/checkbox.tsx.tmpl +28 -0
  182. package/dist/templates/components/ui/dialog.tsx.tmpl +104 -0
  183. package/dist/templates/components/ui/dropdown-menu.tsx.tmpl +183 -0
  184. package/dist/templates/components/ui/input.tsx.tmpl +21 -0
  185. package/dist/templates/components/ui/label.tsx.tmpl +25 -0
  186. package/dist/templates/components/ui/popover.tsx.tmpl +30 -0
  187. package/dist/templates/components/ui/radio-group.tsx.tmpl +44 -0
  188. package/dist/templates/components/ui/select.tsx.tmpl +150 -0
  189. package/dist/templates/components/ui/separator.tsx.tmpl +30 -0
  190. package/dist/templates/components/ui/sheet.tsx.tmpl +125 -0
  191. package/dist/templates/components/ui/skeleton.tsx.tmpl +15 -0
  192. package/dist/templates/components/ui/sonner.tsx.tmpl +30 -0
  193. package/dist/templates/components/ui/switch.tsx.tmpl +29 -0
  194. package/dist/templates/components/ui/table.tsx.tmpl +107 -0
  195. package/dist/templates/components/ui/tabs.tsx.tmpl +55 -0
  196. package/dist/templates/components/ui/textarea.tsx.tmpl +24 -0
  197. package/dist/templates/components/ui/tooltip.tsx.tmpl +30 -0
  198. package/dist/templates/components.json.tmpl +21 -0
  199. package/dist/templates/env.d.ts.tmpl +32 -0
  200. package/dist/templates/lib/admin/actions.ts.tmpl +43 -0
  201. package/dist/templates/lib/admin/context.tsx.tmpl +209 -0
  202. package/dist/templates/lib/admin/nav.ts.tmpl +23 -0
  203. package/dist/templates/lib/auth.config.fallback.ts.tmpl +10 -0
  204. package/dist/templates/lib/auth.config.ts.tmpl +45 -0
  205. package/dist/templates/lib/blocks/translations.ts.tmpl +44 -0
  206. package/dist/templates/lib/blog/translations.ts.tmpl +52 -0
  207. package/dist/templates/lib/content/models.ts.tmpl +53 -0
  208. package/dist/templates/lib/i18n/config.ts.tmpl +18 -0
  209. package/dist/templates/lib/i18n/index.ts.tmpl +1 -0
  210. package/dist/templates/lib/locale-contract/built-in.ts.tmpl +19 -0
  211. package/dist/templates/lib/locale-contract/index.ts.tmpl +3 -0
  212. package/dist/templates/lib/locale-contract/paths.ts.tmpl +29 -0
  213. package/dist/templates/lib/pages/model.ts.tmpl +16 -0
  214. package/dist/templates/lib/pages/source.ts.tmpl +566 -0
  215. package/dist/templates/lib/pages/translations.ts.tmpl +34 -0
  216. package/dist/templates/lib/search/config.fallback.ts.tmpl +11 -0
  217. package/dist/templates/lib/search/config.ts.tmpl +25 -0
  218. package/dist/templates/lib/site/config.ts.tmpl +120 -0
  219. package/dist/templates/lib/site/request-env.ts.tmpl +71 -0
  220. package/dist/templates/lib/site/settings.fallback.ts.tmpl +21 -0
  221. package/dist/templates/lib/site/settings.ts.tmpl +320 -0
  222. package/dist/templates/lib/site/translations.ts.tmpl +30 -0
  223. package/dist/templates/lib/utils.ts.tmpl +9 -0
  224. package/dist/templates/migrations/0001_init.sql.tmpl +57 -0
  225. package/dist/templates/migrations/0002_admin_seed.sql.tmpl +30 -0
  226. package/dist/templates/migrations/0003_search_index.sql.tmpl +29 -0
  227. package/dist/templates/next.config.ts.tmpl +18 -0
  228. package/dist/templates/package.json.tmpl +40 -0
  229. package/dist/templates/shims/cloudflare-workers-empty.mjs +4 -0
  230. package/dist/templates/shims/next-headers-empty.mjs +4 -0
  231. package/dist/templates/tests/smoke.test.ts.tmpl +83 -0
  232. package/dist/templates/tsconfig.json.tmpl +31 -0
  233. package/dist/templates/vite.config.ts.tmpl +53 -0
  234. package/dist/templates/vitest.config.ts.tmpl +13 -0
  235. package/dist/templates/worker/index.ts.tmpl +52 -0
  236. package/dist/templates/wrangler.jsonc.tmpl +44 -0
  237. package/dist/ui-presets.js +60 -0
  238. package/dist/ui-presets.js.map +1 -0
  239. package/package.json +60 -0
@@ -0,0 +1,107 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ const Table = React.forwardRef<
5
+ HTMLTableElement,
6
+ React.HTMLAttributes<HTMLTableElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div className="relative w-full overflow-auto">
9
+ <table
10
+ ref={ref}
11
+ className={cn("w-full caption-bottom text-sm", className)}
12
+ {...props}
13
+ />
14
+ </div>
15
+ ));
16
+ Table.displayName = "Table";
17
+
18
+ const TableHeader = React.forwardRef<
19
+ HTMLTableSectionElement,
20
+ React.HTMLAttributes<HTMLTableSectionElement>
21
+ >(({ className, ...props }, ref) => (
22
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
23
+ ));
24
+ TableHeader.displayName = "TableHeader";
25
+
26
+ const TableBody = React.forwardRef<
27
+ HTMLTableSectionElement,
28
+ React.HTMLAttributes<HTMLTableSectionElement>
29
+ >(({ className, ...props }, ref) => (
30
+ <tbody
31
+ ref={ref}
32
+ className={cn("[&_tr:last-child]:border-0", className)}
33
+ {...props}
34
+ />
35
+ ));
36
+ TableBody.displayName = "TableBody";
37
+
38
+ const TableFooter = React.forwardRef<
39
+ HTMLTableSectionElement,
40
+ React.HTMLAttributes<HTMLTableSectionElement>
41
+ >(({ className, ...props }, ref) => (
42
+ <tfoot
43
+ ref={ref}
44
+ className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
45
+ {...props}
46
+ />
47
+ ));
48
+ TableFooter.displayName = "TableFooter";
49
+
50
+ const TableRow = React.forwardRef<
51
+ HTMLTableRowElement,
52
+ React.HTMLAttributes<HTMLTableRowElement>
53
+ >(({ className, ...props }, ref) => (
54
+ <tr
55
+ ref={ref}
56
+ className={cn("border-b transition-colors hover:bg-muted/50", className)}
57
+ {...props}
58
+ />
59
+ ));
60
+ TableRow.displayName = "TableRow";
61
+
62
+ const TableHead = React.forwardRef<
63
+ HTMLTableCellElement,
64
+ React.ThHTMLAttributes<HTMLTableCellElement>
65
+ >(({ className, ...props }, ref) => (
66
+ <th
67
+ ref={ref}
68
+ className={cn(
69
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground",
70
+ className
71
+ )}
72
+ {...props}
73
+ />
74
+ ));
75
+ TableHead.displayName = "TableHead";
76
+
77
+ const TableCell = React.forwardRef<
78
+ HTMLTableCellElement,
79
+ React.TdHTMLAttributes<HTMLTableCellElement>
80
+ >(({ className, ...props }, ref) => (
81
+ <td ref={ref} className={cn("p-4 align-middle", className)} {...props} />
82
+ ));
83
+ TableCell.displayName = "TableCell";
84
+
85
+ const TableCaption = React.forwardRef<
86
+ HTMLTableCaptionElement,
87
+ React.HTMLAttributes<HTMLTableCaptionElement>
88
+ >(({ className, ...props }, ref) => (
89
+ <caption
90
+ ref={ref}
91
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
92
+ {...props}
93
+ />
94
+ ));
95
+ TableCaption.displayName = "TableCaption";
96
+
97
+ export {
98
+ Table,
99
+ TableHeader,
100
+ TableBody,
101
+ TableFooter,
102
+ TableHead,
103
+ TableRow,
104
+ TableCell,
105
+ TableCaption,
106
+ };
107
+
@@ -0,0 +1,55 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const Tabs = TabsPrimitive.Root;
8
+
9
+ const TabsList = React.forwardRef<
10
+ React.ElementRef<typeof TabsPrimitive.List>,
11
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
12
+ >(({ className, ...props }, ref) => (
13
+ <TabsPrimitive.List
14
+ ref={ref}
15
+ className={cn(
16
+ "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ ));
22
+ TabsList.displayName = TabsPrimitive.List.displayName;
23
+
24
+ const TabsTrigger = React.forwardRef<
25
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
26
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
27
+ >(({ className, ...props }, ref) => (
28
+ <TabsPrimitive.Trigger
29
+ ref={ref}
30
+ className={cn(
31
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
32
+ className
33
+ )}
34
+ {...props}
35
+ />
36
+ ));
37
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
38
+
39
+ const TabsContent = React.forwardRef<
40
+ React.ElementRef<typeof TabsPrimitive.Content>,
41
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
42
+ >(({ className, ...props }, ref) => (
43
+ <TabsPrimitive.Content
44
+ ref={ref}
45
+ className={cn(
46
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ ));
52
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
53
+
54
+ export { Tabs, TabsList, TabsTrigger, TabsContent };
55
+
@@ -0,0 +1,24 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ export interface TextareaProps
5
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
6
+
7
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ "flex min-h-20 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
13
+ className
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+ );
21
+ Textarea.displayName = "Textarea";
22
+
23
+ export { Textarea };
24
+
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const TooltipProvider = TooltipPrimitive.Provider;
8
+ const Tooltip = TooltipPrimitive.Root;
9
+ const TooltipTrigger = TooltipPrimitive.Trigger;
10
+
11
+ const TooltipContent = React.forwardRef<
12
+ React.ElementRef<typeof TooltipPrimitive.Content>,
13
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
14
+ >(({ className, sideOffset = 4, ...props }, ref) => (
15
+ <TooltipPrimitive.Portal>
16
+ <TooltipPrimitive.Content
17
+ ref={ref}
18
+ sideOffset={sideOffset}
19
+ className={cn(
20
+ "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95",
21
+ className
22
+ )}
23
+ {...props}
24
+ />
25
+ </TooltipPrimitive.Portal>
26
+ ));
27
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName;
28
+
29
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
30
+
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
@@ -0,0 +1,32 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+
3
+ // Bindings declared in `wrangler.jsonc`. Add more entries here as
4
+ // you wire up new bindings (KV, R2, queues, durable objects, etc.).
5
+
6
+ interface Env {
7
+ ASSETS: Fetcher;
8
+ IMAGES: ImagesBinding;
9
+ DB: D1Database;
10
+ CONTENT_CACHE: KVNamespace;
11
+ VINEXT_KV_CACHE: KVNamespace;
12
+ ASSETS_BUCKET: R2Bucket;
13
+ NOTION_TOKEN?: string;
14
+ NOTION_DATA_SOURCE_ID?: string;
15
+ NOTION_PAGES_DATA_SOURCE_ID?: string;
16
+ // Read by `lib/site/settings.ts`. The scaffolder writes the
17
+ // real id into wrangler.jsonc#vars. Leave the empty string
18
+ // here as a sentinel for "Notion not configured" — the loader
19
+ // will fall back to the static values in `lib/site/config.ts`.
20
+ NOTION_SITE_SETTINGS_DATA_SOURCE_ID?: string;
21
+ SITE_URL?: string;
22
+ }
23
+
24
+ interface ImagesBinding {
25
+ input(stream: ReadableStream): {
26
+ transform(options: Record<string, unknown>): {
27
+ output(options: { format: string; quality: number }): Promise<{
28
+ response(): Response;
29
+ }>;
30
+ };
31
+ };
32
+ }
@@ -0,0 +1,43 @@
1
+ "use server";
2
+
3
+ import { redirect } from "next/navigation";
4
+ import {
5
+ changeUserPassword,
6
+ getCurrentUser,
7
+ setUserSessionCookie,
8
+ userToSession,
9
+ validatePasswordStrength,
10
+ } from "@notionx/core/auth";
11
+
12
+ export async function changePasswordAction(formData: FormData): Promise<void> {
13
+ const user = await getCurrentUser();
14
+ if (!user) redirect("/login");
15
+
16
+ const currentPassword = String(formData.get("currentPassword") ?? "");
17
+ const newPassword = String(formData.get("newPassword") ?? "");
18
+ const confirmPassword = String(formData.get("confirmPassword") ?? "");
19
+
20
+ const passwordError = validatePasswordStrength(newPassword);
21
+ if (passwordError) {
22
+ redirect(`/admin/account?error=${encodeURIComponent(passwordError)}`);
23
+ }
24
+ if (newPassword !== confirmPassword) {
25
+ redirect("/admin/account?error=两次输入的新密码不一致");
26
+ }
27
+
28
+ const result = await changeUserPassword({
29
+ userId: user.uid,
30
+ currentPassword,
31
+ newPassword,
32
+ });
33
+
34
+ if (!result.ok) {
35
+ if (result.reason === "no_password") {
36
+ redirect("/admin/account?error=当前账号未设置密码");
37
+ }
38
+ redirect("/admin/account?error=当前密码不正确");
39
+ }
40
+
41
+ await setUserSessionCookie(userToSession(result.user));
42
+ redirect("/admin/account?saved=1");
43
+ }
@@ -0,0 +1,209 @@
1
+ import type { ReactNode } from "react";
2
+ import { Badge } from "@/components/ui/badge";
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from "@/components/ui/card";
11
+ import { Input } from "@/components/ui/input";
12
+ import { Label } from "@/components/ui/label";
13
+ import { Separator } from "@/components/ui/separator";
14
+ import { Skeleton } from "@/components/ui/skeleton";
15
+ import {
16
+ contentSources,
17
+ managedContentSources,
18
+ {{contentSourceVarName}},
19
+ } from "@/lib/content/models";
20
+ import { siteConfig } from "@/lib/site/config";
21
+ import { changePasswordAction } from "./actions";
22
+ import {
23
+ getAuthViewer,
24
+ getCurrentUser,
25
+ getUserById,
26
+ } from "@notionx/core/auth";
27
+ import { getContentModelAdminSummaries } from "@notionx/core/content";
28
+ import {
29
+ getNotionEditBaseUrl,
30
+ listGenericNotionContent,
31
+ } from "@notionx/core/notion";
32
+ import type { AdminPageContext } from "@notionx/core/admin/pages";
33
+ import { getAppSettings, isAdminEmail } from "@notionx/core/internal-admin";
34
+ import { getSiteUrl, workerEnv } from "@notionx/core/util";
35
+ import { cn } from "@/lib/utils";
36
+
37
+ function Table({ children }: { children: ReactNode }) {
38
+ return (
39
+ <div className="w-full overflow-auto">
40
+ <table className="w-full caption-bottom text-sm">{children}</table>
41
+ </div>
42
+ );
43
+ }
44
+
45
+ function TableHeader({ children }: { children: ReactNode }) {
46
+ return <thead className="[&_tr]:border-b">{children}</thead>;
47
+ }
48
+
49
+ function TableBody({ children }: { children: ReactNode }) {
50
+ return <tbody className="[&_tr:last-child]:border-0">{children}</tbody>;
51
+ }
52
+
53
+ function TableRow({ children }: { children: ReactNode }) {
54
+ return (
55
+ <tr className="border-b transition-colors hover:bg-muted/50">
56
+ {children}
57
+ </tr>
58
+ );
59
+ }
60
+
61
+ function TableHead({
62
+ className,
63
+ children,
64
+ }: {
65
+ className?: string;
66
+ children?: ReactNode;
67
+ }) {
68
+ return (
69
+ <th
70
+ className={cn(
71
+ "h-10 px-4 text-left align-middle font-medium text-muted-foreground",
72
+ className
73
+ )}
74
+ >
75
+ {children}
76
+ </th>
77
+ );
78
+ }
79
+
80
+ function TableCell({
81
+ className,
82
+ children,
83
+ colSpan,
84
+ }: {
85
+ className?: string;
86
+ children?: ReactNode;
87
+ colSpan?: number;
88
+ }) {
89
+ return (
90
+ <td className={cn("p-4 align-middle", className)} colSpan={colSpan}>
91
+ {children}
92
+ </td>
93
+ );
94
+ }
95
+
96
+ function Alert({
97
+ variant,
98
+ className,
99
+ children,
100
+ }: {
101
+ variant?: "default" | "destructive";
102
+ className?: string;
103
+ children: ReactNode;
104
+ }) {
105
+ return (
106
+ <div
107
+ className={cn(
108
+ "rounded-md border px-4 py-3 text-sm",
109
+ variant === "destructive"
110
+ ? "border-destructive/50 text-destructive"
111
+ : "border-border",
112
+ className
113
+ )}
114
+ role="alert"
115
+ >
116
+ {children}
117
+ </div>
118
+ );
119
+ }
120
+
121
+ function AlertTitle({ children }: { children: ReactNode }) {
122
+ return <div className="mb-1 font-medium leading-none">{children}</div>;
123
+ }
124
+
125
+ function AlertDescription({ children }: { children: ReactNode }) {
126
+ return <div className="text-sm opacity-90">{children}</div>;
127
+ }
128
+
129
+ function Passthrough({ children }: { children: ReactNode }) {
130
+ return <>{children}</>;
131
+ }
132
+
133
+ async function getNotionPostsMeta() {
134
+ const items = await listGenericNotionContent({{contentSourceVarName}});
135
+ return items.map((item) => ({
136
+ slug: item.slug,
137
+ title: item.title,
138
+ author: "Notion",
139
+ date: item.date,
140
+ tags: item.tags,
141
+ editUrl: item.editUrl,
142
+ }));
143
+ }
144
+
145
+ async function getAdminViewer() {
146
+ const viewer = await getAuthViewer();
147
+ if (!viewer) return null;
148
+ return {
149
+ viewer: { email: viewer.email },
150
+ viewerEmail: viewer.email,
151
+ admin: viewer.isAdmin,
152
+ };
153
+ }
154
+
155
+ export function buildAdminPageContext(): AdminPageContext {
156
+ return {
157
+ ui: {
158
+ Button: Button as AdminPageContext["ui"]["Button"],
159
+ Input: Input as AdminPageContext["ui"]["Input"],
160
+ Label: Label as AdminPageContext["ui"]["Label"],
161
+ Card: Card as AdminPageContext["ui"]["Card"],
162
+ CardHeader: CardHeader as AdminPageContext["ui"]["CardHeader"],
163
+ CardTitle: CardTitle as AdminPageContext["ui"]["CardTitle"],
164
+ CardDescription:
165
+ CardDescription as AdminPageContext["ui"]["CardDescription"],
166
+ CardContent: CardContent as AdminPageContext["ui"]["CardContent"],
167
+ Badge: Badge as AdminPageContext["ui"]["Badge"],
168
+ Table,
169
+ TableHeader,
170
+ TableBody,
171
+ TableRow,
172
+ TableHead,
173
+ TableCell,
174
+ Alert,
175
+ AlertTitle,
176
+ AlertDescription,
177
+ Separator: Separator as AdminPageContext["ui"]["Separator"],
178
+ Skeleton: Skeleton as AdminPageContext["ui"]["Skeleton"],
179
+ AlertDialog: Passthrough,
180
+ AlertDialogTrigger: Passthrough,
181
+ AlertDialogContent: Passthrough,
182
+ AlertDialogHeader: Passthrough,
183
+ AlertDialogTitle: Passthrough,
184
+ AlertDialogDescription: Passthrough,
185
+ AlertDialogFooter: Passthrough,
186
+ AlertDialogAction: Passthrough,
187
+ AlertDialogCancel: Passthrough,
188
+ },
189
+ actions: {
190
+ changePassword: changePasswordAction,
191
+ },
192
+ data: {
193
+ getNotionPostsMeta,
194
+ getNotionEditBaseUrl,
195
+ getAdminViewer,
196
+ isAdminEmail,
197
+ getCurrentUser,
198
+ getAppSettings,
199
+ getSiteUrl,
200
+ workerEnv: { TURNSTILE_SECRET_KEY: workerEnv.TURNSTILE_SECRET_KEY },
201
+ getUserById,
202
+ getContentModelAdminSummaries: () =>
203
+ getContentModelAdminSummaries(managedContentSources),
204
+ },
205
+ extra: {
206
+ siteName: siteConfig.name,
207
+ },
208
+ };
209
+ }
@@ -0,0 +1,23 @@
1
+ // Sidebar navigation for `/admin/*`. The package's `createAdminNav`
2
+ // factory sorts items by `order` and filters out any that require a
3
+ // role the viewer does not have.
4
+
5
+ import { createAdminNav } from "@notionx/core/admin";
6
+
7
+ export const adminNav = createAdminNav([
8
+ { href: "/admin", labelKey: "admin.nav.dashboard", icon: "Home", order: 10 },
9
+ {
10
+ href: "/admin/content-models",
11
+ labelKey: "admin.nav.models",
12
+ icon: "Database",
13
+ order: 20,
14
+ },
15
+ {
16
+ href: "/admin/account",
17
+ labelKey: "admin.nav.account",
18
+ icon: "User",
19
+ order: 60,
20
+ },
21
+ ]);
22
+
23
+ export default adminNav;
@@ -0,0 +1,10 @@
1
+ // Auth is disabled in this project (scaffolded with `--no-auth` or
2
+ // `notionx remove auth`). This stub exports `undefined` so the
3
+ // worker entry point can pass it to `createNotionxWorker`, which
4
+ // skips session cookie reading and the admin gate entirely.
5
+
6
+ import type { AuthConfig } from "@notionx/core/types";
7
+
8
+ export const authConfig: AuthConfig | undefined = undefined;
9
+
10
+ export default authConfig;
@@ -0,0 +1,45 @@
1
+ // Auth configuration consumed by `createAuth` and the auth helpers
2
+ // from `@notionx/core`. The package uses the `databaseBinding`
3
+ // name to look up the D1 binding on the platform runtime, the
4
+ // `tables` map to know which tables back users, sessions, password
5
+ // resets, email verifications, and rate-limit buckets, and the
6
+ // `sessionCookie` / `roles` / `turnstile` / `email` / `oauth` blocks
7
+ // to bind the corresponding cookie names, role names, captcha
8
+ // settings, email provider, and OAuth client to the auth helpers.
9
+
10
+ import type { AuthConfig } from "@notionx/core/types";
11
+
12
+ export const authConfig: AuthConfig = {
13
+ databaseBinding: "DB",
14
+ tables: {
15
+ users: "users",
16
+ sessions: "sessions",
17
+ passwordResets: "password_resets",
18
+ emailVerifications: "email_verifications",
19
+ authRateLimits: "auth_rate_limits",
20
+ },
21
+ sessionCookie: {
22
+ name: "vinext_session",
23
+ maxAge: 60 * 60 * 24 * 7,
24
+ secure: true,
25
+ },
26
+ turnstile: {
27
+ siteKeyEnv: "TURNSTILE_SITE_KEY",
28
+ secretKeyEnv: "TURNSTILE_SECRET_KEY",
29
+ },
30
+ email: {
31
+ provider: "resend",
32
+ fromEnv: "RESEND_FROM",
33
+ apiKeyEnv: "RESEND_API_KEY",
34
+ },
35
+ oauth: {
36
+ google: {
37
+ clientIdEnv: "GOOGLE_CLIENT_ID",
38
+ clientSecretEnv: "GOOGLE_CLIENT_SECRET",
39
+ },
40
+ },
41
+ roles: { default: "user", vip: "vip", admin: "admin" },
42
+ password: { minLength: 8 },
43
+ };
44
+
45
+ export default authConfig;
@@ -0,0 +1,44 @@
1
+ // Locale-aware block lookup. Uses the default-locale fallback rule:
2
+ // a block without a translation in the target locale resolves to the
3
+ // default-locale copy so page shells stay usable.
4
+
5
+ import {
6
+ pickTranslation,
7
+ pickTranslationOrDefault,
8
+ } from "@notionx/core";
9
+ import { i18n } from "@/lib/i18n";
10
+ import { blocksContract } from "@/lib/locale-contract";
11
+
12
+ export type BlockTranslation = {
13
+ pageId: string;
14
+ sourcePageId: string;
15
+ locale: string;
16
+ title: string;
17
+ description: string;
18
+ eyebrow: string;
19
+ headline: string;
20
+ subheadline: string;
21
+ body: string;
22
+ quote: string;
23
+ quoteAttribution: string;
24
+ primaryCtaLabel: string;
25
+ primaryCtaHref: string;
26
+ secondaryCtaLabel: string;
27
+ secondaryCtaHref: string;
28
+ published: boolean;
29
+ };
30
+
31
+ export function pickBlockTranslation(
32
+ rows: readonly BlockTranslation[],
33
+ locale: string
34
+ ) {
35
+ return (
36
+ pickTranslation(rows, locale, blocksContract, i18n.defaultLocale) ??
37
+ pickTranslationOrDefault(
38
+ rows,
39
+ locale,
40
+ i18n.defaultLocale,
41
+ blocksContract
42
+ )
43
+ );
44
+ }