create-next-pro-cli 0.1.4 → 0.1.6
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.
- package/package.json +5 -1
- package/src/index.ts +10 -454
- package/src/lib/addComponent.ts +113 -0
- package/src/lib/addPage.ts +170 -0
- package/src/lib/createProject.ts +18 -0
- package/src/lib/createProjectWithPrompt.ts +79 -0
- package/src/lib/rmPage.ts +52 -0
- package/src/lib/utils.ts +36 -0
- package/src/save-index.ts +520 -0
- package/templates/Page/loading.tsx +2 -7
- package/templates/Page/not-found.tsx +11 -0
- package/templates/Page/page-ui.tsx +1 -1
- package/templates/Projects/default/messages/en/{dashboard.json → Dashboard.json} +2 -1
- package/templates/Projects/default/messages/en/_global_ui.json +4 -0
- package/templates/Projects/default/messages/fr/{dashboard.json → Dashboard.json} +2 -1
- package/templates/Projects/default/messages/fr/_global_ui.json +4 -0
- package/templates/Projects/default/package.json +8 -2
- package/templates/Projects/default/src/app/[locale]/(public)/Login/page.tsx +6 -0
- package/templates/Projects/default/src/app/[locale]/(public)/Register/page.tsx +6 -0
- package/templates/Projects/default/src/app/[locale]/(public)/_home/loading.tsx +5 -1
- package/templates/Projects/default/src/app/[locale]/(public)/_home/page.tsx +3 -13
- package/templates/Projects/default/src/app/[locale]/(public)/layout.tsx +0 -2
- package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/loading.tsx +10 -0
- package/templates/Projects/default/src/app/[locale]/(user)/Dashboard/page.tsx +5 -0
- package/templates/Projects/default/src/app/[locale]/(user)/Settings/page.tsx +6 -0
- package/templates/Projects/default/src/app/[locale]/(user)/UserInfo/page.tsx +6 -0
- package/templates/Projects/default/src/app/[locale]/(user)/layout.tsx +0 -3
- package/templates/Projects/default/src/app/[locale]/layout.tsx +3 -4
- package/templates/Projects/default/src/app/[locale]/loading.tsx +3 -6
- package/templates/Projects/default/src/app/[locale]/page.tsx +1 -111
- package/templates/Projects/default/src/app/api/auth/[...nextauth]/route.ts +6 -25
- package/templates/Projects/default/src/app/api/auth/post-login/route.ts +1 -2
- package/templates/Projects/default/src/app/styles/globals.css +295 -16
- package/templates/Projects/default/src/lib/auth/disconnect.ts +11 -0
- package/templates/Projects/default/src/lib/auth/isConnected.ts +5 -3
- package/templates/Projects/default/src/lib/utils.ts +6 -0
- package/templates/Projects/default/src/ui/Dashboard/LogoutButton.tsx +27 -0
- package/templates/Projects/default/src/ui/{dashboard/page.tsx → Dashboard/page-ui.tsx} +5 -4
- package/templates/Projects/default/src/{app/[locale]/(public)/login/page.tsx → ui/Login/page-ui.tsx} +7 -4
- package/templates/Projects/default/src/{app/[locale]/(public)/register/page.tsx → ui/Register/page-ui.tsx} +7 -4
- package/templates/Projects/default/src/ui/Settings/page-ui.tsx +17 -0
- package/templates/Projects/default/src/ui/UserInfo/page-ui.tsx +17 -0
- package/templates/Projects/default/src/ui/_global/BackButton.tsx +5 -3
- package/templates/Projects/default/src/ui/_global/Button.tsx +75 -0
- package/templates/Projects/default/src/ui/_global/GlobalHeader.tsx +42 -21
- package/templates/Projects/default/src/ui/_global/GlobalMain.tsx +3 -3
- package/templates/Projects/default/src/ui/_global/Loading.tsx +13 -0
- package/templates/Projects/default/src/ui/_global/LocaleSwitcher.tsx +1 -1
- package/templates/Projects/default/src/ui/_global/PublicNav.tsx +74 -13
- package/templates/Projects/default/src/ui/_global/ThemeToggle.tsx +53 -0
- package/templates/Projects/default/src/ui/_global/UserNav.tsx +8 -5
- package/templates/Projects/default/src/ui/_home/page-ui.tsx +27 -0
- package/templates/Projects/default/public/cnp-logo.ico +0 -0
- package/templates/Projects/default/public/file.svg +0 -1
- package/templates/Projects/default/public/globe.svg +0 -1
- package/templates/Projects/default/public/next.svg +0 -1
- package/templates/Projects/default/public/vercel.svg +0 -1
- package/templates/Projects/default/public/window.svg +0 -1
- package/templates/Projects/default/src/app/[locale]/(user)/dashboard/loading.tsx +0 -17
- package/templates/Projects/default/src/app/[locale]/(user)/dashboard/page.tsx +0 -12
- package/templates/Projects/default/src/app/[locale]/(user)/settings/page.tsx +0 -12
- package/templates/Projects/default/src/app/[locale]/(user)/user_info/page.tsx +0 -12
- package/templates/Projects/default/src/app/api/auth/[...nextauth].ts +0 -0
- package/templates/Projects/default/src/app/api/me/route.ts +0 -15
- /package/templates/Projects/default/messages/en/{login.json → Login.json} +0 -0
- /package/templates/Projects/default/messages/en/{register.json → Register.json} +0 -0
- /package/templates/Projects/default/messages/en/{settings.json → Settings.json} +0 -0
- /package/templates/Projects/default/messages/en/{user_info.json → UserInfo.json} +0 -0
- /package/templates/Projects/default/messages/fr/{login.json → Login.json} +0 -0
- /package/templates/Projects/default/messages/fr/{register.json → Register.json} +0 -0
- /package/templates/Projects/default/messages/fr/{settings.json → Settings.json} +0 -0
- /package/templates/Projects/default/messages/fr/{user_info.json → UserInfo.json} +0 -0
- /package/templates/Projects/default/src/app/[locale]/(user)/{dashboard → Dashboard}/error.tsx +0 -0
- /package/templates/Projects/default/src/app/[locale]/(user)/{settings → Settings}/loading.tsx +0 -0
- /package/templates/Projects/default/src/app/[locale]/(user)/{user_info → UserInfo}/loading.tsx +0 -0
- /package/templates/Projects/default/src/ui/{dashboard → Dashboard}/StatsCard.tsx +0 -0
- /package/templates/Projects/default/src/ui/{dashboard → Dashboard}/WelcomeCard.tsx +0 -0
package/templates/Projects/default/src/{app/[locale]/(public)/login/page.tsx → ui/Login/page-ui.tsx}
RENAMED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
// src/ui/Login/page-ui.tsx
|
|
1
2
|
"use client";
|
|
3
|
+
|
|
2
4
|
import { useTranslations } from "next-intl";
|
|
3
5
|
import { signIn } from "next-auth/react";
|
|
4
6
|
|
|
5
|
-
export default function
|
|
6
|
-
const t = useTranslations("
|
|
7
|
+
export default function LoginPageUI() {
|
|
8
|
+
const t = useTranslations("Login");
|
|
9
|
+
|
|
7
10
|
return (
|
|
8
|
-
<
|
|
11
|
+
<section className="py-8 px-4 max-w-3xl mx-auto">
|
|
9
12
|
<h1 className="text-2xl font-bold">{t("title")}</h1>
|
|
10
13
|
<p className="text-muted mt-2">{t("description")}</p>
|
|
11
14
|
<button
|
|
@@ -14,6 +17,6 @@ export default function LoginPage() {
|
|
|
14
17
|
>
|
|
15
18
|
{t("login_with_google")}
|
|
16
19
|
</button>
|
|
17
|
-
</
|
|
20
|
+
</section>
|
|
18
21
|
);
|
|
19
22
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
// src/ui/Register/page-ui.tsx
|
|
1
2
|
"use client";
|
|
3
|
+
|
|
2
4
|
import { useTranslations } from "next-intl";
|
|
3
5
|
import { signIn } from "next-auth/react";
|
|
4
6
|
import BackButton from "@/ui/_global/BackButton";
|
|
5
7
|
|
|
6
|
-
export default function
|
|
7
|
-
const t = useTranslations("
|
|
8
|
+
export default function RegisterPageUI() {
|
|
9
|
+
const t = useTranslations("Register");
|
|
10
|
+
|
|
8
11
|
return (
|
|
9
|
-
<
|
|
12
|
+
<section className="py-8 px-4 max-w-md mx-auto">
|
|
10
13
|
<div className="flex items-center justify-between mb-6">
|
|
11
14
|
<h1 className="text-2xl font-bold">{t("title")}</h1>
|
|
12
15
|
<BackButton />
|
|
@@ -18,6 +21,6 @@ export default function RegisterPage() {
|
|
|
18
21
|
>
|
|
19
22
|
{t("register_with_google")}
|
|
20
23
|
</button>
|
|
21
|
-
</
|
|
24
|
+
</section>
|
|
22
25
|
);
|
|
23
26
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/ui/Settings/page-ui.tsx
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
|
|
6
|
+
export default function SettingsPageUI() {
|
|
7
|
+
const t = useTranslations("Settings");
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<section className="py-8 px-4 max-w-3xl mx-auto">
|
|
12
|
+
<h1 className="text-2xl font-bold">{t("title")}</h1>
|
|
13
|
+
<p className="text-muted mt-2">{t("description")}</p>
|
|
14
|
+
</section>
|
|
15
|
+
</>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/ui/UserInfo/page-ui.tsx
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
|
|
6
|
+
export default function UserInfoPageUI() {
|
|
7
|
+
const t = useTranslations("UserInfo");
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<section className="py-8 px-4 max-w-3xl mx-auto">
|
|
12
|
+
<h1 className="text-2xl font-bold">{t("title")}</h1>
|
|
13
|
+
<p className="text-muted mt-2">{t("description")}</p>
|
|
14
|
+
</section>
|
|
15
|
+
</>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use client"; // DO NOT FORGET , this is a client component.
|
|
2
2
|
import { useRouter } from "next/navigation";
|
|
3
|
+
import { StepBackIcon } from "lucide-react";
|
|
4
|
+
import { Button } from "./Button";
|
|
3
5
|
|
|
4
6
|
export default function BackButton({ className = "" }: { className?: string }) {
|
|
5
7
|
const router = useRouter();
|
|
6
8
|
return (
|
|
7
|
-
<
|
|
9
|
+
<Button
|
|
8
10
|
type="button"
|
|
9
11
|
onClick={() => router.back()}
|
|
10
12
|
className={`inline-flex items-center gap-2 px-3 py-1.5 rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition ${className}`}
|
|
11
13
|
>
|
|
12
|
-
<
|
|
13
|
-
</
|
|
14
|
+
<StepBackIcon className="h-4 w-4" /> Retour
|
|
15
|
+
</Button>
|
|
14
16
|
);
|
|
15
17
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/ui/_global/Button.tsx
|
|
2
|
+
"use client";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
+
import { cva } from "class-variance-authority";
|
|
6
|
+
import React from "react";
|
|
7
|
+
|
|
8
|
+
const buttonVariants = cva(
|
|
9
|
+
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer disabled:pointer-events-none disabled:opacity-50",
|
|
10
|
+
{
|
|
11
|
+
variants: {
|
|
12
|
+
variant: {
|
|
13
|
+
default:
|
|
14
|
+
"bg-primary text-primary-foreground hover:bg-primary/90 dark:text-white",
|
|
15
|
+
destructive:
|
|
16
|
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
17
|
+
outline:
|
|
18
|
+
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
19
|
+
secondary:
|
|
20
|
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
21
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
22
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
23
|
+
negative:
|
|
24
|
+
"bg-black text-white dark:bg-white dark:text-black hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors",
|
|
25
|
+
},
|
|
26
|
+
size: {
|
|
27
|
+
default: "h-10 px-4 py-2",
|
|
28
|
+
sm: "h-9 rounded-md px-3",
|
|
29
|
+
lg: "h-11 rounded-md px-8",
|
|
30
|
+
icon: "h-10 w-10",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
size: "default",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
import type { ButtonHTMLAttributes, DetailedHTMLProps, ReactNode } from "react";
|
|
41
|
+
|
|
42
|
+
type ButtonProps = {
|
|
43
|
+
className?: string;
|
|
44
|
+
variant?:
|
|
45
|
+
| "default"
|
|
46
|
+
| "destructive"
|
|
47
|
+
| "outline"
|
|
48
|
+
| "secondary"
|
|
49
|
+
| "ghost"
|
|
50
|
+
| "link";
|
|
51
|
+
size?: "default" | "sm" | "lg" | "icon";
|
|
52
|
+
asChild?: boolean;
|
|
53
|
+
children?: ReactNode;
|
|
54
|
+
} & DetailedHTMLProps<
|
|
55
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
56
|
+
HTMLButtonElement
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
60
|
+
({ className, variant, size, asChild = false, children, ...props }, ref) => {
|
|
61
|
+
const Comp = asChild ? Slot : "button";
|
|
62
|
+
return (
|
|
63
|
+
<Comp
|
|
64
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
65
|
+
ref={ref}
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
{children}
|
|
69
|
+
</Comp>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
Button.displayName = "Button";
|
|
74
|
+
|
|
75
|
+
export { Button, buttonVariants };
|
|
@@ -1,33 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import Image from "next/image";
|
|
2
3
|
import UserNav from "@/ui/_global/UserNav";
|
|
3
4
|
import LocaleSwitcher from "@/ui/_global/LocaleSwitcher";
|
|
4
5
|
import PublicNav from "@/ui/_global/PublicNav";
|
|
6
|
+
import { useEffect, useState } from "react";
|
|
7
|
+
import { Link } from "@/lib/i18n/navigation";
|
|
8
|
+
import { isConnected } from "@/lib/auth/isConnected";
|
|
9
|
+
|
|
10
|
+
type GlobalHeaderProps = {
|
|
11
|
+
isConnectedInitial: boolean;
|
|
12
|
+
};
|
|
5
13
|
|
|
6
14
|
export default function GlobalHeader({
|
|
7
|
-
|
|
8
|
-
}: {
|
|
9
|
-
|
|
10
|
-
|
|
15
|
+
isConnectedInitial,
|
|
16
|
+
}: GlobalHeaderProps) {
|
|
17
|
+
const [isConnectedState, setIsConnectedState] = useState(isConnectedInitial);
|
|
18
|
+
|
|
19
|
+
// Vérification dynamique côté client (exemple via /api/me)
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
async function checkConnection() {
|
|
22
|
+
setIsConnectedState(await isConnected());
|
|
23
|
+
}
|
|
24
|
+
checkConnection();
|
|
25
|
+
// Optionnel : timer pour rafraîchir périodiquement
|
|
26
|
+
// const interval = setInterval(checkConnection, 60000);
|
|
27
|
+
// return () => clearInterval(interval);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
11
30
|
return (
|
|
12
|
-
<header className="
|
|
31
|
+
<header className="fixed top-0 w-full z-50 glass-effect border-b">
|
|
13
32
|
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
<Link href="/" className="">
|
|
34
|
+
<div className="flex items-center h-16">
|
|
35
|
+
<Image
|
|
36
|
+
src="/cnp-logo.svg"
|
|
37
|
+
alt="Logo"
|
|
38
|
+
width={63}
|
|
39
|
+
height={63}
|
|
40
|
+
className="mr-2"
|
|
41
|
+
/>
|
|
42
|
+
<h1 className="text-lg font-semibold tracking-tight select-none">
|
|
43
|
+
Create Next Pro
|
|
44
|
+
</h1>
|
|
45
|
+
</div>
|
|
46
|
+
</Link>
|
|
47
|
+
|
|
48
|
+
{isConnectedState ? <UserNav /> : <PublicNav />}
|
|
49
|
+
<div className="flex items-center h-16">
|
|
29
50
|
<LocaleSwitcher />
|
|
30
|
-
</
|
|
51
|
+
</div>
|
|
31
52
|
</div>
|
|
32
53
|
</header>
|
|
33
54
|
);
|
|
@@ -6,10 +6,10 @@ export default function GlobalMain({
|
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
}) {
|
|
8
8
|
return (
|
|
9
|
-
<main className="min-h-screen bg-neutral-50 font-sans text-gray-900">
|
|
10
|
-
<
|
|
9
|
+
<main className="mt-0 min-h-screen bg-neutral-50 font-sans text-gray-900 flex flex-col">
|
|
10
|
+
<div className="flex-1 flex flex-col justify-center items-stretch mx-auto w-full px-6 self-stretch">
|
|
11
11
|
{children}
|
|
12
|
-
</
|
|
12
|
+
</div>
|
|
13
13
|
</main>
|
|
14
14
|
);
|
|
15
15
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client"; // DO NOT FORGET , this is a client component.
|
|
2
|
+
import { useTranslations } from "next-intl";
|
|
3
|
+
|
|
4
|
+
const Loading = () => {
|
|
5
|
+
const t = useTranslations("_global_ui");
|
|
6
|
+
return (
|
|
7
|
+
<div className="rounded border bg-white p-6 shadow-sm">
|
|
8
|
+
<h2 className="text-lg font-semibold mb-2">{t("Loading.title")}</h2>
|
|
9
|
+
<p className="text-gray-600">{t("Loading.description")}</p>
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
export default Loading;
|
|
@@ -23,7 +23,7 @@ export default function LocaleSwitcher() {
|
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<select
|
|
26
|
-
className="ml-2
|
|
26
|
+
className="ml-2 ring rounded px-2 py-1 text-sm dark:bg-gray-800 dark:text-gray-200"
|
|
27
27
|
value={currentLocale}
|
|
28
28
|
onChange={handleChange}
|
|
29
29
|
aria-label="Select language"
|
|
@@ -1,29 +1,90 @@
|
|
|
1
1
|
"use client"; // DO NOT FORGET , this is a client component.
|
|
2
2
|
import { Link } from "@/lib/i18n/navigation";
|
|
3
3
|
import { useTranslations } from "next-intl";
|
|
4
|
+
import { useEffect, useState, useRef } from "react";
|
|
5
|
+
import { Button } from "@/ui/_global/Button";
|
|
6
|
+
import ThemeToggle from "@/ui/_global/ThemeToggle";
|
|
7
|
+
import { Menu, X } from "lucide-react";
|
|
4
8
|
|
|
5
9
|
const navLinks = [
|
|
6
10
|
{ href: "/", labelKey: "public_nav.links.home" },
|
|
7
|
-
{ href: "/
|
|
8
|
-
{ href: "/
|
|
11
|
+
{ href: "/Login", labelKey: "public_nav.links.login" },
|
|
12
|
+
{ href: "/Register", labelKey: "public_nav.links.register" },
|
|
9
13
|
];
|
|
10
14
|
|
|
11
15
|
const PublicNav = () => {
|
|
12
16
|
const t = useTranslations("_global_ui");
|
|
17
|
+
const [open, setOpen] = useState(false);
|
|
18
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
21
|
+
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
|
22
|
+
setOpen(false);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (open) {
|
|
27
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
32
|
+
};
|
|
33
|
+
}, [open]);
|
|
34
|
+
|
|
13
35
|
return (
|
|
14
36
|
<nav aria-label="Public navigation">
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
<div className="flex justify-between items-center h-16">
|
|
38
|
+
<ul className="flex gap-2 md:gap-4 hidden md:flex items-center space-x-4">
|
|
39
|
+
{navLinks.map((link) => (
|
|
40
|
+
<li key={link.href}>
|
|
41
|
+
<Link
|
|
42
|
+
href={link.href}
|
|
43
|
+
className="inline-block rounded px-3 py-1.5 text-sm font-medium dark:text-gray-400 light:text-gray-700 hover:text-blue-600 hover:bg-blue-50 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
|
|
44
|
+
>
|
|
45
|
+
{t(link.labelKey)}
|
|
46
|
+
</Link>
|
|
47
|
+
</li>
|
|
48
|
+
))}
|
|
49
|
+
</ul>
|
|
50
|
+
<div className="flex items-center space-x-4">
|
|
51
|
+
<ThemeToggle />
|
|
52
|
+
</div>
|
|
53
|
+
<div className="md:hidden flex items-center">
|
|
54
|
+
<Button
|
|
55
|
+
variant="default"
|
|
56
|
+
onClick={() => setOpen((o) => !o)}
|
|
57
|
+
className="focus:outline-none"
|
|
58
|
+
aria-label="Menu"
|
|
59
|
+
>
|
|
60
|
+
{open ? <X className="h-7 w-7" /> : <Menu className="h-7 w-7" />}
|
|
61
|
+
</Button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
{open && (
|
|
66
|
+
<div className="fixed inset-0 z-40 md:hidden">
|
|
67
|
+
<div className="absolute inset-0 bg-black/40" aria-hidden="true" />
|
|
68
|
+
<div
|
|
69
|
+
ref={menuRef}
|
|
70
|
+
className="mobil-menu absolute top-4 left-1/2 -translate-x-1/2 flex flex-col gap-2 rounded-xl p-4 shadow-lg animate-fade-in z-50 w-11/12 max-w-xs"
|
|
71
|
+
>
|
|
72
|
+
<Link href="/Login" onClick={() => setOpen(false)}>
|
|
73
|
+
<Button
|
|
74
|
+
variant="ghost"
|
|
75
|
+
className="w-full text-white justify-center hover:text-blue-300 cursor-pointer glass-effect"
|
|
76
|
+
>
|
|
77
|
+
{t("public_nav.links.login")}
|
|
78
|
+
</Button>
|
|
79
|
+
</Link>
|
|
80
|
+
<Link href="/Register" onClick={() => setOpen(false)}>
|
|
81
|
+
<Button className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 justify-center cursor-pointer">
|
|
82
|
+
{t("public_nav.links.register")}
|
|
83
|
+
</Button>
|
|
23
84
|
</Link>
|
|
24
|
-
</
|
|
25
|
-
|
|
26
|
-
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
27
88
|
</nav>
|
|
28
89
|
);
|
|
29
90
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Moon, Sun } from "lucide-react";
|
|
4
|
+
import { Button } from "./Button";
|
|
5
|
+
|
|
6
|
+
export default function ThemeToggle() {
|
|
7
|
+
const [isDark, setIsDark] = useState(false);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
// Initial state from html class
|
|
11
|
+
setIsDark(document.documentElement.classList.contains("dark"));
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
const toggleTheme = () => {
|
|
15
|
+
const html = document.documentElement;
|
|
16
|
+
if (html.classList.contains("dark")) {
|
|
17
|
+
html.classList.add("light");
|
|
18
|
+
html.classList.remove("dark");
|
|
19
|
+
setIsDark(false);
|
|
20
|
+
localStorage.setItem("theme", "light");
|
|
21
|
+
} else {
|
|
22
|
+
html.classList.add("dark");
|
|
23
|
+
html.classList.remove("light");
|
|
24
|
+
setIsDark(true);
|
|
25
|
+
localStorage.setItem("theme", "dark");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
// On mount, set theme from localStorage
|
|
31
|
+
const theme = localStorage.getItem("theme");
|
|
32
|
+
if (theme === "dark") {
|
|
33
|
+
document.documentElement.classList.add("dark");
|
|
34
|
+
document.documentElement.classList.remove("light");
|
|
35
|
+
setIsDark(true);
|
|
36
|
+
} else if (theme === "light") {
|
|
37
|
+
document.documentElement.classList.add("light");
|
|
38
|
+
document.documentElement.classList.remove("dark");
|
|
39
|
+
setIsDark(false);
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Button
|
|
45
|
+
onClick={toggleTheme}
|
|
46
|
+
className="ml-2 p-2 rounded-full hover:bg-white/10 transition-colors "
|
|
47
|
+
aria-label="Toggle theme"
|
|
48
|
+
type="button"
|
|
49
|
+
>
|
|
50
|
+
{isDark ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
|
|
51
|
+
</Button>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
"use client"; // DO NOT FORGET , this is a client component.
|
|
2
2
|
import { Link } from "@/lib/i18n/navigation";
|
|
3
3
|
import { useTranslations } from "next-intl";
|
|
4
|
+
import LogoutButton from "../Dashboard/LogoutButton";
|
|
4
5
|
|
|
5
6
|
const navLinks = [
|
|
6
|
-
{ href: "/
|
|
7
|
-
{ href: "/
|
|
8
|
-
{ href: "/
|
|
9
|
-
{ href: "/", labelKey: "user_nav.links.home" },
|
|
7
|
+
{ href: "/Dashboard", labelKey: "user_nav.links.dashboard" },
|
|
8
|
+
{ href: "/Settings", labelKey: "user_nav.links.settings" },
|
|
9
|
+
{ href: "/UserInfo", labelKey: "user_nav.links.user_info" },
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
const UserNav = () => {
|
|
13
13
|
const t = useTranslations("_global_ui");
|
|
14
14
|
return (
|
|
15
15
|
<nav aria-label="User navigation">
|
|
16
|
-
<ul className="flex gap-2 md:gap-4">
|
|
16
|
+
<ul className="flex gap-2 md:gap-4 h-16 items-center">
|
|
17
17
|
{navLinks.map((link) => (
|
|
18
18
|
<li key={link.href}>
|
|
19
19
|
<Link
|
|
@@ -24,6 +24,9 @@ const UserNav = () => {
|
|
|
24
24
|
</Link>
|
|
25
25
|
</li>
|
|
26
26
|
))}
|
|
27
|
+
<li>
|
|
28
|
+
<LogoutButton />
|
|
29
|
+
</li>
|
|
27
30
|
</ul>
|
|
28
31
|
</nav>
|
|
29
32
|
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/ui/_home/page-ui.tsx
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
|
|
6
|
+
export default function HomePageUI() {
|
|
7
|
+
const t = useTranslations("_home");
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<section className="pt-32 pb-20 px-4 sm:px-6 lg:px-8 hero-pattern">
|
|
12
|
+
<div className="max-w-7xl mx-auto">
|
|
13
|
+
<div className="grid lg:grid-cols-2 gap-12 items-center">
|
|
14
|
+
<h1 className="text-5xl lg:text-6xl font-bold mb-6 leading-tight">
|
|
15
|
+
{t.rich("title", {
|
|
16
|
+
span: (chunks) => (
|
|
17
|
+
<span className="gradient-text">{chunks}</span>
|
|
18
|
+
),
|
|
19
|
+
})}
|
|
20
|
+
</h1>
|
|
21
|
+
<p className="text-lg text-gray-400">{t("description")}</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</section>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// app/[locale]/dashboard/loading.tsx
|
|
2
|
-
"use client";
|
|
3
|
-
|
|
4
|
-
export default function Loading() {
|
|
5
|
-
return (
|
|
6
|
-
<div className="flex flex-col flex-1 items-center justify-center">
|
|
7
|
-
<p className="text-gray-500">Loading dashboard...</p>
|
|
8
|
-
</div>
|
|
9
|
-
);
|
|
10
|
-
}
|
|
11
|
-
// This component is used to show a loading state while the dashboard is being prepared
|
|
12
|
-
// You can customize it with a spinner, skeletons, or any loading UI you prefer
|
|
13
|
-
// It will be displayed when the dashboard is loading, for example during data fetching or component rendering
|
|
14
|
-
// This is useful for providing feedback to users that something is happening in the background
|
|
15
|
-
// You can also use this to implement a more complex loading state with animations or placeholders
|
|
16
|
-
// Make sure to keep it lightweight to avoid blocking the main thread
|
|
17
|
-
// This component is part of the Next.js app directory structure and will be automatically used by Next.js
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import Dashboard from "@/ui/dashboard/page";
|
|
2
|
-
|
|
3
|
-
export default function DashboardPage() {
|
|
4
|
-
return <Dashboard />;
|
|
5
|
-
}
|
|
6
|
-
// This page is the main entry point for the dashboard
|
|
7
|
-
// It uses the `useTranslations` hook to fetch localized strings
|
|
8
|
-
// The `t` function is used to get the translated strings for the dashboard
|
|
9
|
-
// You can add more components or logic here to build your dashboard
|
|
10
|
-
// This is part of the Next.js app directory structure
|
|
11
|
-
// It will be automatically rendered when the user navigates to the dashboard
|
|
12
|
-
// Make sure to define the translations in your locale files
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useTranslations } from "next-intl";
|
|
3
|
-
|
|
4
|
-
export default function UserInfoPage() {
|
|
5
|
-
const t = useTranslations("settings");
|
|
6
|
-
return (
|
|
7
|
-
<main className="py-8 px-4 max-w-3xl mx-auto">
|
|
8
|
-
<h1 className="text-2xl font-bold">{t("title")}</h1>
|
|
9
|
-
<p className="text-muted mt-2">{t("description")}</p>
|
|
10
|
-
</main>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useTranslations } from "next-intl";
|
|
3
|
-
|
|
4
|
-
export default function UserInfoPage() {
|
|
5
|
-
const t = useTranslations("user_info");
|
|
6
|
-
return (
|
|
7
|
-
<main className="py-8 px-4 max-w-3xl mx-auto">
|
|
8
|
-
<h1 className="text-2xl font-bold">{t("title")}</h1>
|
|
9
|
-
<p className="text-muted mt-2">{t("description")}</p>
|
|
10
|
-
</main>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
File without changes
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
-
import { verify } from "jsonwebtoken";
|
|
3
|
-
|
|
4
|
-
export async function GET(req: NextRequest) {
|
|
5
|
-
const cookie = req.cookies.get("app_token")?.value;
|
|
6
|
-
if (!cookie) {
|
|
7
|
-
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
8
|
-
}
|
|
9
|
-
try {
|
|
10
|
-
const user = verify(cookie, process.env.APP_JWT_SECRET!);
|
|
11
|
-
return NextResponse.json({ user });
|
|
12
|
-
} catch {
|
|
13
|
-
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
|
|
14
|
-
}
|
|
15
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|