@thinhnguyencth1204/nextcli 0.9.0 → 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 (56) hide show
  1. package/dist/cli.js +3 -3
  2. package/package.json +1 -1
  3. package/templates/features/api/src/lib/api/axios.ts +1 -90
  4. package/templates/features/auth/messages/vi/auth.json +2 -1
  5. package/templates/features/auth/src/app/(auth)/change-password/page.tsx +5 -4
  6. package/templates/features/auth/src/app/(auth)/layout.tsx +2 -5
  7. package/templates/features/auth/src/app/(auth)/sign-in/page.tsx +5 -4
  8. package/templates/features/auth/src/app/api/v1/auth/login/route.ts +24 -29
  9. package/templates/features/auth/src/app/api/v1/auth/logout/route.ts +0 -5
  10. package/templates/features/auth/src/components/layout/auth/auth-shell.tsx +24 -0
  11. package/templates/features/auth/src/features/auth/components/account-panel.tsx +15 -3
  12. package/templates/features/auth/src/features/auth/components/change-password-form.tsx +27 -30
  13. package/templates/features/auth/src/features/auth/components/sign-in-form.tsx +33 -42
  14. package/templates/features/auth/src/lib/auth/client.ts +2 -2
  15. package/templates/features/auth/src/lib/auth/server.ts +2 -2
  16. package/templates/features/dashboard/src/app/(dashboard)/account/page.tsx +9 -7
  17. package/templates/features/dashboard/src/app/(dashboard)/dashboard/page.tsx +24 -10
  18. package/templates/features/dashboard/src/components/layout/private/app-sidebar.tsx +1 -13
  19. package/templates/features/dashboard/src/components/layout/private/dashboard-layout.tsx +31 -22
  20. package/templates/features/dashboard/src/components/layout/private/page-shell.tsx +40 -0
  21. package/templates/features/database/prisma/schema.prisma +1 -0
  22. package/templates/features/example/messages/vi/example.json +11 -1
  23. package/templates/features/example/src/app/(dashboard)/example/page.tsx +92 -3
  24. package/templates/features/example/src/example/components/example-table.tsx +15 -2
  25. package/templates/next-base/bun.lock +407 -0
  26. package/templates/next-base/messages/vi/auth.json +2 -1
  27. package/templates/next-base/messages/vi/common.json +19 -0
  28. package/templates/next-base/messages/vi/example.json +11 -1
  29. package/templates/next-base/next-env.d.ts +1 -1
  30. package/templates/next-base/prisma/schema.prisma +1 -0
  31. package/templates/next-base/src/app/(auth)/change-password/page.tsx +5 -4
  32. package/templates/next-base/src/app/(auth)/layout.tsx +2 -5
  33. package/templates/next-base/src/app/(auth)/sign-in/page.tsx +5 -4
  34. package/templates/next-base/src/app/(dashboard)/account/page.tsx +9 -7
  35. package/templates/next-base/src/app/(dashboard)/dashboard/page.tsx +24 -10
  36. package/templates/next-base/src/app/(dashboard)/example/page.tsx +92 -3
  37. package/templates/next-base/src/app/api/v1/auth/login/route.ts +24 -29
  38. package/templates/next-base/src/app/api/v1/auth/logout/route.ts +0 -5
  39. package/templates/next-base/src/components/branding/logo.tsx +27 -4
  40. package/templates/next-base/src/components/layout/auth/auth-shell.tsx +24 -0
  41. package/templates/next-base/src/components/layout/private/app-sidebar.tsx +1 -13
  42. package/templates/next-base/src/components/layout/private/dashboard-layout.tsx +31 -22
  43. package/templates/next-base/src/components/layout/private/page-shell.tsx +40 -0
  44. package/templates/next-base/src/example/components/example-table.tsx +15 -2
  45. package/templates/next-base/src/features/auth/components/account-panel.tsx +15 -3
  46. package/templates/next-base/src/features/auth/components/change-password-form.tsx +27 -30
  47. package/templates/next-base/src/features/auth/components/sign-in-form.tsx +33 -42
  48. package/templates/next-base/src/lib/api/axios.ts +1 -90
  49. package/templates/next-base/src/lib/auth/client.ts +2 -2
  50. package/templates/next-base/src/lib/auth/server.ts +2 -2
  51. package/templates/features/api/src/lib/api/token-store.ts +0 -13
  52. package/templates/features/auth/src/app/api/v1/auth/refresh/route.ts +0 -32
  53. package/templates/features/auth/src/lib/auth/cookies.ts +0 -15
  54. package/templates/next-base/src/app/api/v1/auth/refresh/route.ts +0 -32
  55. package/templates/next-base/src/lib/api/token-store.ts +0 -13
  56. package/templates/next-base/src/lib/auth/cookies.ts +0 -15
@@ -1,14 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { ChevronLeft, ChevronRight } from "lucide-react";
4
- import Link from "next/link";
5
- import {
6
- useSidebar,
7
- Sidebar,
8
- SidebarHeader,
9
- SidebarTrigger,
10
- } from "@/components/ui/sidebar";
11
- import { Logo } from "@/components/branding/logo";
4
+ import { useSidebar, Sidebar, SidebarTrigger } from "@/components/ui/sidebar";
12
5
  import { NavSidebar } from "@/components/layout/private/nav-sidebar";
13
6
  import { cn } from "@/utils/cn";
14
7
 
@@ -33,11 +26,6 @@ export function AppSidebar() {
33
26
  </SidebarTrigger>
34
27
  </div>
35
28
  )}
36
- <SidebarHeader className="p-3">
37
- <Link href="/dashboard">
38
- <Logo showLabel />
39
- </Link>
40
- </SidebarHeader>
41
29
  <NavSidebar />
42
30
  </Sidebar>
43
31
  );
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { Moon, Sun } from "lucide-react";
3
+ import { Menu, Moon, Sun } from "lucide-react";
4
4
  import { useTheme } from "next-themes";
5
5
  import { useTranslations } from "next-intl";
6
6
  import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
@@ -19,33 +19,42 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
19
19
  return (
20
20
  <SidebarProvider>
21
21
  <div className="flex h-screen w-full flex-col">
22
- <header className="flex h-14 items-center justify-between border-b bg-card px-4">
23
- <div className="flex items-center gap-2">
24
- {isMobile && <SidebarTrigger />}
25
- <Logo showLabel />
26
- </div>
22
+ <header className="relative z-50 flex h-[3.75rem] shrink-0 items-center border-b border-border bg-card">
23
+ <div className="flex w-full items-center justify-between px-4 md:px-6">
24
+ <div className="flex items-center gap-2">
25
+ {isMobile ? (
26
+ <SidebarTrigger variant="outline" size="icon">
27
+ <Menu className="h-5 w-5" />
28
+ </SidebarTrigger>
29
+ ) : null}
30
+ <Logo variant="header" />
31
+ </div>
27
32
 
28
- <div className="flex items-center gap-2">
29
- <Button
30
- variant="ghost"
31
- size="icon"
32
- onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
33
- title={t("toggleTheme")}
34
- >
35
- {theme === "dark" ? (
36
- <Sun className="h-5 w-5" />
37
- ) : (
38
- <Moon className="h-5 w-5" />
39
- )}
40
- </Button>
41
- <NavUser />
33
+ <div className="flex items-center gap-2">
34
+ {!isMobile ? (
35
+ <Button
36
+ variant="ghost"
37
+ size="icon"
38
+ onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
39
+ title={t("toggleTheme")}
40
+ className="rounded-full"
41
+ >
42
+ {theme === "dark" ? (
43
+ <Sun className="h-5 w-5" />
44
+ ) : (
45
+ <Moon className="h-5 w-5" />
46
+ )}
47
+ </Button>
48
+ ) : null}
49
+ <NavUser />
50
+ </div>
42
51
  </div>
43
52
  </header>
44
53
 
45
54
  <div className="flex flex-1 overflow-hidden">
46
55
  <AppSidebar />
47
- <ScrollArea className="flex-1">
48
- <main className="p-4 md:p-6">{children}</main>
56
+ <ScrollArea className="flex-1 bg-background">
57
+ <main>{children}</main>
49
58
  </ScrollArea>
50
59
  </div>
51
60
  </div>
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "@/utils/cn";
3
+
4
+ type PageShellProps = {
5
+ title: string;
6
+ description?: string;
7
+ actions?: ReactNode;
8
+ children: ReactNode;
9
+ className?: string;
10
+ };
11
+
12
+ export function PageShell({
13
+ title,
14
+ description,
15
+ actions,
16
+ children,
17
+ className,
18
+ }: PageShellProps) {
19
+ return (
20
+ <div
21
+ className={cn(
22
+ "mx-auto w-full max-w-screen-2xl px-4 py-10 sm:px-6 lg:px-10",
23
+ className,
24
+ )}
25
+ >
26
+ <div className="flex flex-col gap-4 pb-6 sm:flex-row sm:items-start sm:justify-between">
27
+ <div className="space-y-1">
28
+ <h1 className="text-3xl font-black tracking-tight">{title}</h1>
29
+ {description ? (
30
+ <p className="text-sm text-muted-foreground">{description}</p>
31
+ ) : null}
32
+ </div>
33
+ {actions ? (
34
+ <div className="flex shrink-0 items-center gap-2">{actions}</div>
35
+ ) : null}
36
+ </div>
37
+ {children}
38
+ </div>
39
+ );
40
+ }
@@ -70,5 +70,6 @@ model Verification {
70
70
  createdAt DateTime? @default(now())
71
71
  updatedAt DateTime? @updatedAt
72
72
  }
73
+
73
74
  // Optional Chatbox models are appended by NexTCLI only when adding the chat feature.
74
75
  // Review generated schema changes before running migrations on production databases.
@@ -1,6 +1,16 @@
1
1
  {
2
2
  "page": {
3
- "title": "Ví dụ"
3
+ "title": "Ví dụ",
4
+ "description": "Trang mẫu với bảng dữ liệu, thẻ nội dung và nút thêm bản ghi.",
5
+ "add": "Thêm mới",
6
+ "createTitle": "Thêm bản ghi ví dụ",
7
+ "nameLabel": "Tên",
8
+ "descriptionLabel": "Mô tả",
9
+ "createSubmit": "Lưu",
10
+ "creating": "Đang lưu...",
11
+ "createSuccess": "Đã thêm bản ghi ví dụ.",
12
+ "createFailed": "Không thể thêm bản ghi ví dụ.",
13
+ "invalidInput": "Vui lòng nhập tên hợp lệ."
4
14
  },
5
15
  "table": {
6
16
  "name": "Tên",
@@ -1,13 +1,102 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import type { FormEvent } from "react";
1
5
  import { useTranslations } from "next-intl";
6
+ import { Plus } from "lucide-react";
7
+ import { toast } from "sonner";
8
+ import { useCreateExample } from "@/example/api/use-mutations";
9
+ import { createExampleSchema } from "@/example/validations";
2
10
  import { ExampleTable } from "@/example/components/example-table";
11
+ import { PageShell } from "@/components/layout/private/page-shell";
12
+ import { Button } from "@/components/ui/button";
13
+ import {
14
+ Dialog,
15
+ DialogContent,
16
+ DialogFooter,
17
+ DialogHeader,
18
+ DialogTitle,
19
+ DialogTrigger,
20
+ } from "@/components/ui/dialog";
21
+ import { Input } from "@/components/ui/input";
22
+ import { Label } from "@/components/ui/label";
3
23
 
4
24
  export default function ExamplePage() {
5
25
  const t = useTranslations("example.page");
26
+ const createExample = useCreateExample();
27
+ const [open, setOpen] = useState(false);
28
+ const [name, setName] = useState("");
29
+ const [description, setDescription] = useState("");
30
+
31
+ const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
32
+ event.preventDefault();
33
+
34
+ const parsed = createExampleSchema.safeParse({ name, description });
35
+ if (!parsed.success) {
36
+ toast.error(t("invalidInput"));
37
+ return;
38
+ }
39
+
40
+ try {
41
+ await createExample.mutateAsync(parsed.data);
42
+ toast.success(t("createSuccess"));
43
+ setName("");
44
+ setDescription("");
45
+ setOpen(false);
46
+ } catch (error) {
47
+ const message =
48
+ error instanceof Error ? error.message : t("createFailed");
49
+ toast.error(message);
50
+ }
51
+ };
6
52
 
7
53
  return (
8
- <main className="space-y-4">
9
- <h1 className="text-2xl font-semibold">{t("title")}</h1>
54
+ <PageShell
55
+ title={t("title")}
56
+ description={t("description")}
57
+ actions={
58
+ <Dialog open={open} onOpenChange={setOpen}>
59
+ <DialogTrigger asChild>
60
+ <Button>
61
+ <Plus className="mr-2 h-4 w-4" />
62
+ {t("add")}
63
+ </Button>
64
+ </DialogTrigger>
65
+ <DialogContent>
66
+ <DialogHeader>
67
+ <DialogTitle>{t("createTitle")}</DialogTitle>
68
+ </DialogHeader>
69
+ <form onSubmit={onSubmit} className="space-y-4">
70
+ <div className="space-y-2">
71
+ <Label htmlFor="example-name">{t("nameLabel")}</Label>
72
+ <Input
73
+ id="example-name"
74
+ value={name}
75
+ onChange={(event) => setName(event.target.value)}
76
+ required
77
+ />
78
+ </div>
79
+ <div className="space-y-2">
80
+ <Label htmlFor="example-description">
81
+ {t("descriptionLabel")}
82
+ </Label>
83
+ <Input
84
+ id="example-description"
85
+ value={description}
86
+ onChange={(event) => setDescription(event.target.value)}
87
+ />
88
+ </div>
89
+ <DialogFooter>
90
+ <Button type="submit" disabled={createExample.isPending}>
91
+ {createExample.isPending ? t("creating") : t("createSubmit")}
92
+ </Button>
93
+ </DialogFooter>
94
+ </form>
95
+ </DialogContent>
96
+ </Dialog>
97
+ }
98
+ >
10
99
  <ExampleTable />
11
- </main>
100
+ </PageShell>
12
101
  );
13
102
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from "@tanstack/react-table";
11
11
  import { useExample } from "@/example/api/use-example";
12
12
  import { DataTable } from "@/components/ui/data-table/data-table";
13
+ import { Card, CardContent } from "@/components/ui/card";
13
14
 
14
15
  type ExampleItem = {
15
16
  id: string;
@@ -44,8 +45,20 @@ export function ExampleTable() {
44
45
  });
45
46
 
46
47
  if (isLoading) {
47
- return <p>{t("loading")}</p>;
48
+ return (
49
+ <Card>
50
+ <CardContent className="py-8 text-sm text-muted-foreground">
51
+ {t("loading")}
52
+ </CardContent>
53
+ </Card>
54
+ );
48
55
  }
49
56
 
50
- return <DataTable table={table} />;
57
+ return (
58
+ <Card>
59
+ <CardContent className="pt-6">
60
+ <DataTable table={table} />
61
+ </CardContent>
62
+ </Card>
63
+ );
51
64
  }