@thinhnguyencth1204/nextcli 0.6.1 → 0.8.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 (122) hide show
  1. package/README.md +68 -47
  2. package/dist/cli.js +1002 -753
  3. package/package.json +6 -2
  4. package/templates/{next-base/src/lib/axios-instance.ts → features/api/src/lib/api/axios.ts} +7 -2
  5. package/templates/{next-base/src/lib/api-response.ts → features/api/src/lib/api/response.ts} +1 -5
  6. package/templates/{next-base → features/auth}/src/app/(auth)/change-password/layout.tsx +1 -1
  7. package/templates/{next-base → features/auth}/src/app/(auth)/sign-in/layout.tsx +1 -1
  8. package/templates/{next-base → features/auth}/src/app/api/v1/auth/change-password/route.ts +3 -3
  9. package/templates/{next-base → features/auth}/src/app/api/v1/auth/login/route.ts +3 -3
  10. package/templates/{next-base → features/auth}/src/app/api/v1/auth/logout/route.ts +2 -2
  11. package/templates/{next-base → features/auth}/src/app/api/v1/auth/me/route.ts +2 -2
  12. package/templates/{next-base → features/auth}/src/app/api/v1/auth/refresh/route.ts +2 -2
  13. package/templates/{next-base → features/auth}/src/app/api/v1/users/[id]/route.ts +3 -3
  14. package/templates/{next-base → features/auth}/src/app/api/v1/users/route.ts +3 -3
  15. package/templates/{next-base → features/auth}/src/features/auth/components/account-panel.tsx +1 -1
  16. package/templates/{next-base → features/auth}/src/features/auth/components/change-password-form.tsx +1 -1
  17. package/templates/{next-base → features/auth}/src/features/auth/components/sign-in-form.tsx +2 -2
  18. package/templates/{next-base → features/auth}/src/features/users/services.ts +1 -1
  19. package/templates/{next-base → features/auth}/src/instrumentation.ts +1 -1
  20. package/templates/{next-base/src/lib → features/auth/src/lib/auth}/bootstrap.ts +2 -3
  21. package/templates/features/auth/src/lib/auth/index.ts +1 -0
  22. package/templates/{next-base/src/lib → features/auth/src/lib/auth}/rbac.ts +2 -5
  23. package/templates/{next-base/src/lib/auth.ts → features/auth/src/lib/auth/server.ts} +2 -1
  24. package/templates/{next-base → features/auth}/src/lib/constants.ts +3 -0
  25. package/templates/features/chat/src/app/api/v1/chat/route.ts +1 -1
  26. package/templates/features/chat/src/features/chat/api/use-chat-history.ts +1 -1
  27. package/templates/features/chat/src/features/chat/api/use-send-message.ts +1 -1
  28. package/templates/{next-base → features/dashboard}/src/app/(dashboard)/layout.tsx +1 -1
  29. package/templates/features/dashboard/src/app/page.tsx +5 -0
  30. package/templates/{next-base → features/dashboard}/src/components/layout/private/nav-user.tsx +1 -1
  31. package/templates/{next-base → features/database}/prisma/schema.prisma +0 -12
  32. package/templates/{next-base → features/database}/prisma.config.ts +2 -2
  33. package/templates/features/database/src/lib/prisma.ts +23 -0
  34. package/templates/{next-base → features/example}/src/app/api/v1/example/route.ts +2 -2
  35. package/templates/{next-base → features/example}/src/example/api/use-example.ts +1 -1
  36. package/templates/{next-base → features/example}/src/example/api/use-mutations.ts +1 -1
  37. package/templates/{next-base → features/example}/src/example/services.ts +1 -1
  38. package/templates/features/i18n/next.config.ts +17 -0
  39. package/templates/features/i18n/src/app/layout.tsx +42 -0
  40. package/templates/features/supabase/src/lib/supabase/rich-text-image-sync.ts +28 -0
  41. package/templates/next-base/.env +0 -14
  42. package/templates/next-base/.env.development +0 -14
  43. package/templates/next-base/.env.example +0 -14
  44. package/templates/next-base/PROJECT_STRUCTURE.md +43 -54
  45. package/templates/next-base/SETUP.md +12 -60
  46. package/templates/next-base/bun.lock +59 -397
  47. package/templates/next-base/next-env.d.ts +1 -1
  48. package/templates/next-base/next.config.ts +1 -4
  49. package/templates/next-base/nextcli.json +3 -3
  50. package/templates/next-base/package.json +6 -21
  51. package/templates/next-base/src/app/blog-demo/page.tsx +9 -0
  52. package/templates/next-base/src/app/globals.css +57 -0
  53. package/templates/next-base/src/app/layout.tsx +6 -14
  54. package/templates/next-base/src/app/page.tsx +25 -2
  55. package/templates/next-base/src/components/rich-text/adapters/textarea-field.tsx +50 -0
  56. package/templates/next-base/src/components/rich-text/client-only.tsx +23 -0
  57. package/templates/next-base/src/components/rich-text/editor-field.tsx +62 -0
  58. package/templates/next-base/src/components/rich-text/examples/blog-rich-text-demo.tsx +218 -0
  59. package/templates/next-base/src/components/rich-text/index.ts +11 -0
  60. package/templates/next-base/src/components/rich-text/lexical/extension.ts +37 -0
  61. package/templates/next-base/src/components/rich-text/lexical/nodes/image-node.tsx +187 -0
  62. package/templates/next-base/src/components/rich-text/lexical/plugins/image-plugin.tsx +40 -0
  63. package/templates/next-base/src/components/rich-text/lexical/plugins/initial-state-plugin.tsx +26 -0
  64. package/templates/next-base/src/components/rich-text/lexical/plugins/on-change-plugin.tsx +26 -0
  65. package/templates/next-base/src/components/rich-text/lexical/plugins/toolbar-plugin.tsx +190 -0
  66. package/templates/next-base/src/components/rich-text/lexical/rich-text-editor.tsx +121 -0
  67. package/templates/next-base/src/components/rich-text/lexical/theme.ts +18 -0
  68. package/templates/next-base/src/components/rich-text/rich-text-renderer.tsx +72 -0
  69. package/templates/next-base/src/components/rich-text/types.ts +60 -0
  70. package/templates/next-base/src/hooks/index.ts +1 -1
  71. package/templates/next-base/src/lib/rich-text/default-image-removal.ts +10 -0
  72. package/templates/next-base/src/lib/rich-text/image-urls.ts +41 -0
  73. package/templates/next-base/src/lib/rich-text/index.ts +12 -0
  74. package/templates/next-base/src/lib/rich-text/supabase-url.ts +67 -0
  75. package/templates/next-base/src/lib/rich-text/sync-removed-images.ts +48 -0
  76. package/templates/next-base/src/types/index.ts +0 -2
  77. package/templates/next-base/tsconfig.tsbuildinfo +1 -0
  78. package/templates/next-base/prisma/migrations/20260612000000_init/migration.sql +0 -104
  79. package/templates/next-base/prisma/migrations/migration_lock.toml +0 -3
  80. package/templates/next-base/src/app/(auth)/.gitkeep +0 -1
  81. /package/templates/{next-base → features/api}/src/components/providers/query-provider.tsx +0 -0
  82. /package/templates/{next-base/src/lib → features/api/src/lib/api}/token-store.ts +0 -0
  83. /package/templates/{next-base → features/auth}/messages/vi/auth.json +0 -0
  84. /package/templates/{next-base/prisma/migrations → features/auth/src/app/(auth)}/.gitkeep +0 -0
  85. /package/templates/{next-base → features/auth}/src/app/(auth)/change-password/page.tsx +0 -0
  86. /package/templates/{next-base → features/auth}/src/app/(auth)/layout.tsx +0 -0
  87. /package/templates/{next-base → features/auth}/src/app/(auth)/sign-in/page.tsx +0 -0
  88. /package/templates/{next-base → features/auth}/src/app/api/auth/[...all]/route.ts +0 -0
  89. /package/templates/{next-base → features/auth}/src/features/auth/validations.ts +0 -0
  90. /package/templates/{next-base → features/auth}/src/features/users/validations.ts +0 -0
  91. /package/templates/{next-base/src/lib/auth-client.ts → features/auth/src/lib/auth/client.ts} +0 -0
  92. /package/templates/{next-base/src/lib/auth-cookies.ts → features/auth/src/lib/auth/cookies.ts} +0 -0
  93. /package/templates/{next-base → features/dashboard}/src/app/(dashboard)/account/page.tsx +0 -0
  94. /package/templates/{next-base → features/dashboard}/src/app/(dashboard)/dashboard/page.tsx +0 -0
  95. /package/templates/{next-base → features/dashboard}/src/components/layout/private/app-sidebar.tsx +0 -0
  96. /package/templates/{next-base → features/dashboard}/src/components/layout/private/dashboard-layout.tsx +0 -0
  97. /package/templates/{next-base → features/dashboard}/src/components/layout/private/nav-sidebar.tsx +0 -0
  98. /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-column-header.tsx +0 -0
  99. /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-filter-list.tsx +0 -0
  100. /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-pagination.tsx +0 -0
  101. /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-skeleton.tsx +0 -0
  102. /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-toolbar.tsx +0 -0
  103. /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table-view-options.tsx +0 -0
  104. /package/templates/{next-base → features/dashboard}/src/components/ui/data-table/data-table.tsx +0 -0
  105. /package/templates/{next-base → features/dashboard}/src/components/ui/sidebar.tsx +0 -0
  106. /package/templates/{next-base → features/dashboard}/src/data/sidebar-modules.ts +0 -0
  107. /package/templates/{next-base → features/dashboard}/src/hooks/table/use-data-table.ts +0 -0
  108. /package/templates/{next-base → features/dashboard}/src/hooks/use-mobile.ts +0 -0
  109. /package/templates/{next-base → features/dashboard}/src/types/data-table.ts +0 -0
  110. /package/templates/{next-base/src/lib → features/database/src/lib/db}/prisma.ts +0 -0
  111. /package/templates/{next-base → features/example}/messages/vi/example.json +0 -0
  112. /package/templates/{next-base → features/example}/src/app/(dashboard)/example/page.tsx +0 -0
  113. /package/templates/{next-base → features/example}/src/example/components/example-table.tsx +0 -0
  114. /package/templates/{next-base → features/example}/src/example/validations.ts +0 -0
  115. /package/templates/{next-base → features/i18n}/messages/vi/common.json +0 -0
  116. /package/templates/{next-base → features/i18n}/src/components/layout/private/locale-switcher.tsx +0 -0
  117. /package/templates/{next-base → features/i18n}/src/i18n/config.ts +0 -0
  118. /package/templates/{next-base → features/i18n}/src/i18n/namespaces.ts +0 -0
  119. /package/templates/{next-base → features/i18n}/src/i18n/request.ts +0 -0
  120. /package/templates/{next-base → features/supabase}/src/lib/supabase/client.ts +0 -0
  121. /package/templates/{next-base → features/supabase}/src/lib/supabase/storage-config.ts +0 -0
  122. /package/templates/{next-base → features/supabase}/src/lib/supabase/storage.ts +0 -0
@@ -0,0 +1,60 @@
1
+ import type { SerializedEditorState } from "lexical";
2
+
3
+ /** Lexical editor state persisted as JSON (API / database). */
4
+ export type LexicalEditorContent = SerializedEditorState;
5
+
6
+ /** Plain string content for the native textarea adapter. */
7
+ export type TextareaEditorContent = string;
8
+
9
+ export type EditorVariant = "textarea" | "lexical";
10
+
11
+ export type EditorContent = LexicalEditorContent | TextareaEditorContent;
12
+
13
+ export type EditorFieldProps = {
14
+ /** Switch editor implementation without changing form code. */
15
+ variant?: EditorVariant;
16
+ value: EditorContent | null;
17
+ onChange: (value: EditorContent) => void;
18
+ /** Called when image URLs disappear from Lexical content (for storage cleanup). */
19
+ onImagesRemoved?: (removedUrls: string[]) => void;
20
+ placeholder?: string;
21
+ disabled?: boolean;
22
+ readOnly?: boolean;
23
+ error?: string;
24
+ className?: string;
25
+ id?: string;
26
+ name?: string;
27
+ };
28
+
29
+ export function isLexicalContent(
30
+ value: EditorContent | null,
31
+ ): value is LexicalEditorContent {
32
+ return (
33
+ value !== null &&
34
+ typeof value === "object" &&
35
+ "root" in value &&
36
+ typeof value.root === "object"
37
+ );
38
+ }
39
+
40
+ export function createEmptyLexicalContent(): LexicalEditorContent {
41
+ return {
42
+ root: {
43
+ children: [
44
+ {
45
+ children: [],
46
+ direction: null,
47
+ format: "",
48
+ indent: 0,
49
+ type: "paragraph",
50
+ version: 1,
51
+ },
52
+ ],
53
+ direction: null,
54
+ format: "",
55
+ indent: 0,
56
+ type: "root",
57
+ version: 1,
58
+ },
59
+ } as unknown as LexicalEditorContent;
60
+ }
@@ -1 +1 @@
1
- export * from "@/hooks/use-mobile";
1
+ /** Re-export hooks from optional modules here when needed. */
@@ -0,0 +1,10 @@
1
+ import type { RemoveManagedImageFn } from "@/lib/rich-text";
2
+
3
+ /**
4
+ * Default no-op image remover for base-only projects.
5
+ * After `nextcli add module supabase`, use
6
+ * `removeSupabaseManagedImage` from `@/lib/supabase/rich-text-image-sync`.
7
+ */
8
+ export const defaultRemoveManagedImage: RemoveManagedImageFn = async () => {
9
+ // Intentionally empty — wire Supabase removal when the module is installed.
10
+ };
@@ -0,0 +1,41 @@
1
+ import type { SerializedEditorState } from "lexical";
2
+
3
+ type LexicalJsonNode = {
4
+ type?: string;
5
+ src?: string;
6
+ children?: LexicalJsonNode[];
7
+ };
8
+
9
+ function walkNodes(node: LexicalJsonNode, urls: Set<string>): void {
10
+ if (node.type === "image" && typeof node.src === "string" && node.src) {
11
+ urls.add(node.src);
12
+ }
13
+
14
+ if (Array.isArray(node.children)) {
15
+ for (const child of node.children) {
16
+ walkNodes(child, urls);
17
+ }
18
+ }
19
+ }
20
+
21
+ export function extractImageUrlsFromState(
22
+ state: SerializedEditorState | null | undefined,
23
+ ): string[] {
24
+ if (!state?.root) {
25
+ return [];
26
+ }
27
+
28
+ const urls = new Set<string>();
29
+ walkNodes(state.root as LexicalJsonNode, urls);
30
+ return [...urls];
31
+ }
32
+
33
+ export function diffRemovedImageUrls(
34
+ previous: SerializedEditorState | null | undefined,
35
+ next: SerializedEditorState | null | undefined,
36
+ ): string[] {
37
+ const previousUrls = extractImageUrlsFromState(previous);
38
+ const nextUrls = new Set(extractImageUrlsFromState(next));
39
+
40
+ return previousUrls.filter((url) => !nextUrls.has(url));
41
+ }
@@ -0,0 +1,12 @@
1
+ export { diffRemovedImageUrls, extractImageUrlsFromState } from "./image-urls";
2
+ export {
3
+ isSupabaseManagedImageUrl,
4
+ parseSupabaseStorageUrl,
5
+ type SupabaseStorageRef,
6
+ } from "./supabase-url";
7
+ export {
8
+ syncRemovedRichTextImages,
9
+ type RemoveManagedImageFn,
10
+ type SyncRemovedImagesOptions,
11
+ } from "./sync-removed-images";
12
+ export { defaultRemoveManagedImage } from "./default-image-removal";
@@ -0,0 +1,67 @@
1
+ const DEFAULT_BUCKET =
2
+ process.env.NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET?.trim() || "public";
3
+
4
+ export type SupabaseStorageRef = {
5
+ bucket: string;
6
+ path: string;
7
+ };
8
+
9
+ function getConfiguredBucket(): string {
10
+ return (
11
+ process.env.NEXT_PUBLIC_SUPABASE_STORAGE_BUCKET?.trim() || DEFAULT_BUCKET
12
+ );
13
+ }
14
+
15
+ /**
16
+ * Maps a Supabase public object URL back to `{ bucket, path }`.
17
+ * Returns null for external URLs or when Supabase env is not configured.
18
+ */
19
+ export function parseSupabaseStorageUrl(
20
+ url: string,
21
+ ): SupabaseStorageRef | null {
22
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.trim();
23
+ if (!supabaseUrl) {
24
+ return null;
25
+ }
26
+
27
+ try {
28
+ const parsed = new URL(url);
29
+ const base = new URL(supabaseUrl);
30
+
31
+ if (parsed.origin !== base.origin) {
32
+ return null;
33
+ }
34
+
35
+ const publicPrefix = "/storage/v1/object/public/";
36
+ if (!parsed.pathname.startsWith(publicPrefix)) {
37
+ return null;
38
+ }
39
+
40
+ const remainder = parsed.pathname.slice(publicPrefix.length);
41
+ const slashIndex = remainder.indexOf("/");
42
+ if (slashIndex <= 0) {
43
+ return null;
44
+ }
45
+
46
+ const bucket = remainder.slice(0, slashIndex);
47
+ const path = remainder.slice(slashIndex + 1);
48
+
49
+ if (!path) {
50
+ return null;
51
+ }
52
+
53
+ return { bucket, path };
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ export function isSupabaseManagedImageUrl(url: string): boolean {
60
+ const ref = parseSupabaseStorageUrl(url);
61
+ if (!ref) {
62
+ return false;
63
+ }
64
+
65
+ const configuredBucket = getConfiguredBucket();
66
+ return ref.bucket === configuredBucket;
67
+ }
@@ -0,0 +1,48 @@
1
+ import type { SerializedEditorState } from "lexical";
2
+ import { diffRemovedImageUrls } from "./image-urls";
3
+ import {
4
+ isSupabaseManagedImageUrl,
5
+ parseSupabaseStorageUrl,
6
+ } from "./supabase-url";
7
+
8
+ export type RemoveManagedImageFn = (
9
+ url: string,
10
+ ref: { bucket: string; path: string },
11
+ ) => Promise<void>;
12
+
13
+ export type SyncRemovedImagesOptions = {
14
+ /** Custom predicate; defaults to Supabase bucket/path URLs for this project. */
15
+ isManagedUrl?: (url: string) => boolean;
16
+ /** Injected remover (e.g. Supabase `removeResource`). */
17
+ removeManagedImage: RemoveManagedImageFn;
18
+ };
19
+
20
+ /**
21
+ * Compares previous and next Lexical states, then removes storage objects
22
+ * for images that disappeared from content. Non-managed URLs are ignored.
23
+ */
24
+ export async function syncRemovedRichTextImages(
25
+ previous: SerializedEditorState | null | undefined,
26
+ next: SerializedEditorState | null | undefined,
27
+ options: SyncRemovedImagesOptions,
28
+ ): Promise<string[]> {
29
+ const removedUrls = diffRemovedImageUrls(previous, next);
30
+ const isManaged = options.isManagedUrl ?? isSupabaseManagedImageUrl;
31
+ const deleted: string[] = [];
32
+
33
+ for (const url of removedUrls) {
34
+ if (!isManaged(url)) {
35
+ continue;
36
+ }
37
+
38
+ const ref = parseSupabaseStorageUrl(url);
39
+ if (!ref) {
40
+ continue;
41
+ }
42
+
43
+ await options.removeManagedImage(url, ref);
44
+ deleted.push(url);
45
+ }
46
+
47
+ return deleted;
48
+ }
@@ -38,5 +38,3 @@ export type ApiErrorResponse = {
38
38
  };
39
39
 
40
40
  export type ApiResponse<T> = ApiSuccess<T> | ApiErrorResponse;
41
-
42
- export * from "@/types/data-table";