create-next-pro-cli 0.1.28 → 0.1.29

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 (50) hide show
  1. package/README.md +17 -3
  2. package/dist/bin.bun.js +16 -2
  3. package/dist/bin.node.js +16 -2
  4. package/dist/bin.node.js.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/Projects/default/.env.example +8 -6
  7. package/templates/Projects/default/.github/workflows/quality.yml +40 -9
  8. package/templates/Projects/default/.gitignore.template +33 -15
  9. package/templates/Projects/default/README.md +38 -9
  10. package/templates/Projects/default/bun.lock +120 -134
  11. package/templates/Projects/default/messages/en/_home.json +9 -2
  12. package/templates/Projects/default/messages/en/settings.json +9 -1
  13. package/templates/Projects/default/messages/en/userInfo.json +6 -1
  14. package/templates/Projects/default/messages/fr/_home.json +9 -2
  15. package/templates/Projects/default/messages/fr/settings.json +9 -1
  16. package/templates/Projects/default/messages/fr/userInfo.json +6 -1
  17. package/templates/Projects/default/next.config.ts +11 -0
  18. package/templates/Projects/default/package.json +7 -1
  19. package/templates/Projects/default/playwright.config.ts +29 -6
  20. package/templates/Projects/default/pnpm-workspace.yaml +2 -0
  21. package/templates/Projects/default/scripts/audit.ts +4 -0
  22. package/templates/Projects/default/scripts/package-manager.ts +68 -0
  23. package/templates/Projects/default/src/app/[locale]/(public)/_home/page.tsx +2 -2
  24. package/templates/Projects/default/src/app/[locale]/(public)/layout.tsx +1 -2
  25. package/templates/Projects/default/src/app/[locale]/(public)/login/page.tsx +6 -1
  26. package/templates/Projects/default/src/app/[locale]/(public)/register/page.tsx +6 -1
  27. package/templates/Projects/default/src/app/[locale]/(user)/userInfo/page.tsx +5 -2
  28. package/templates/Projects/default/src/app/[locale]/page.tsx +1 -4
  29. package/templates/Projects/default/src/lib/github/repository.ts +68 -0
  30. package/templates/Projects/default/src/ui/_global/GlobalMain.tsx +10 -5
  31. package/templates/Projects/default/src/ui/_global/ThemeToggle.tsx +18 -8
  32. package/templates/Projects/default/src/ui/_global/UserNav.tsx +5 -1
  33. package/templates/Projects/default/src/ui/_home/GitHubActions.tsx +57 -0
  34. package/templates/Projects/default/src/ui/_home/page-shell.tsx +16 -0
  35. package/templates/Projects/default/src/ui/_home/page-ui.tsx +24 -13
  36. package/templates/Projects/default/src/ui/dashboard/LogoutButton.tsx +1 -1
  37. package/templates/Projects/default/src/ui/dashboard/StatsCard.tsx +2 -2
  38. package/templates/Projects/default/src/ui/dashboard/WelcomeCard.tsx +4 -2
  39. package/templates/Projects/default/src/ui/dashboard/page-ui.tsx +0 -2
  40. package/templates/Projects/default/src/ui/settings/page-ui.tsx +45 -6
  41. package/templates/Projects/default/src/ui/userInfo/page-ui.tsx +54 -7
  42. package/templates/Projects/default/tests/consumer/validate-template.ts +57 -48
  43. package/templates/Projects/default/tests/e2e/template-remediation.playwright.ts +47 -4
  44. package/templates/Projects/default/tests/unit/csp.test.ts +1 -1
  45. package/templates/Projects/default/tests/unit/github-repository.test.ts +45 -0
  46. package/templates/Projects/default/tests/unit/package-manager.test.ts +51 -0
  47. package/templates/Projects/default/tests/unit/template-baseline.test.ts +52 -0
  48. package/templates/Projects/default/tests/unit/user-nav.test.tsx +52 -0
  49. package/templates/Projects/default/tsconfig.json +2 -1
  50. package/templates/Projects/default/vitest.config.ts +10 -0
@@ -1,4 +1,11 @@
1
1
  {
2
- "title": "Home",
3
- "description": "Welcome to your homepage"
2
+ "title": "Welcome to your website",
3
+ "description": "Start developing right now.",
4
+ "readme": "Read the Doc",
5
+ "github": {
6
+ "project_credit": "This project was created with create-next-pro-cli",
7
+ "star": "Star",
8
+ "watch": "Watch",
9
+ "action_with_count": "{label} on GitHub, {count} total"
10
+ }
4
11
  }
@@ -1,4 +1,12 @@
1
1
  {
2
2
  "title": "User Settings",
3
- "description": "View and edit your personal settings."
3
+ "description": "Manage your personal preferences.",
4
+ "language": {
5
+ "title": "Language",
6
+ "description": "Choose the language used by the application."
7
+ },
8
+ "theme": {
9
+ "title": "Theme",
10
+ "description": "Switch between the light and dark appearance."
11
+ }
4
12
  }
@@ -1,4 +1,9 @@
1
1
  {
2
2
  "title": "User Information",
3
- "description": "View and edit your personal information."
3
+ "description": "Information provided by your Google account.",
4
+ "name": "Name",
5
+ "email": "Email address",
6
+ "profile_image_alt": "Google profile picture",
7
+ "profile_image_unavailable": "Google profile picture unavailable",
8
+ "unavailable": "Not provided"
4
9
  }
@@ -1,4 +1,11 @@
1
1
  {
2
- "title": "Bienvenue sur notre site",
3
- "description": "Commencez votre exploration dès maintenant."
2
+ "title": "Bienvenue sur votre site",
3
+ "description": "Commencez votre développement dès maintenant.",
4
+ "readme": "Lire la documentation",
5
+ "github": {
6
+ "project_credit": "Ce projet est créé avec create-next-pro-cli",
7
+ "star": "Star",
8
+ "watch": "Watch",
9
+ "action_with_count": "{label} sur GitHub, {count} au total"
10
+ }
4
11
  }
@@ -1,4 +1,12 @@
1
1
  {
2
2
  "title": "Paramètres utilisateur",
3
- "description": "Consultez et modifiez vos paramètres personnels."
3
+ "description": "Gérez vos préférences personnelles.",
4
+ "language": {
5
+ "title": "Langue",
6
+ "description": "Choisissez la langue utilisée par l’application."
7
+ },
8
+ "theme": {
9
+ "title": "Thème",
10
+ "description": "Basculez entre les apparences claire et sombre."
11
+ }
4
12
  }
@@ -1,4 +1,9 @@
1
1
  {
2
2
  "title": "Informations utilisateur",
3
- "description": "Consultez et modifiez vos informations personnelles."
3
+ "description": "Informations fournies par votre compte Google.",
4
+ "name": "Nom",
5
+ "email": "Adresse e-mail",
6
+ "profile_image_alt": "Photo de profil Google",
7
+ "profile_image_unavailable": "Photo de profil Google indisponible",
8
+ "unavailable": "Non renseigné"
4
9
  }
@@ -8,6 +8,17 @@ const contentSecurityPolicy = createContentSecurityPolicy(
8
8
 
9
9
  const nextConfig: NextConfig = {
10
10
  poweredByHeader: false,
11
+ turbopack: {
12
+ root: process.cwd(),
13
+ },
14
+ images: {
15
+ remotePatterns: [
16
+ {
17
+ protocol: "https",
18
+ hostname: "lh3.googleusercontent.com",
19
+ },
20
+ ],
21
+ },
11
22
  async headers() {
12
23
  const headers = [
13
24
  {
@@ -21,7 +21,7 @@
21
21
  "verify:rendering": "node --experimental-strip-types tests/rendering/verify-rendering.ts",
22
22
  "check": "prettier --check . && eslint . && tsc --noEmit && vitest run && next build && node --experimental-strip-types tests/rendering/verify-rendering.ts",
23
23
  "test:consumer": "node --experimental-strip-types tests/consumer/validate-template.ts",
24
- "audit": "bun audit"
24
+ "audit": "node --experimental-strip-types scripts/audit.ts"
25
25
  },
26
26
  "dependencies": {
27
27
  "@radix-ui/react-slot": "1.3.0",
@@ -52,5 +52,11 @@
52
52
  },
53
53
  "overrides": {
54
54
  "postcss": "8.5.15"
55
+ },
56
+ "allowScripts": {
57
+ "@parcel/watcher": true,
58
+ "@swc/core": true,
59
+ "sharp": true,
60
+ "unrs-resolver": true
55
61
  }
56
62
  }
@@ -6,21 +6,44 @@ export default defineConfig({
6
6
  fullyParallel: true,
7
7
  retries: process.env.CI ? 2 : 0,
8
8
  reporter: process.env.CI ? "github" : "list",
9
+ timeout: 60_000,
10
+ expect: {
11
+ timeout: 10_000,
12
+ },
9
13
  use: {
10
14
  baseURL: "http://127.0.0.1:3100",
11
15
  trace: "retain-on-failure",
16
+ contextOptions: { reducedMotion: "reduce" },
12
17
  },
13
- projects: [
14
- { name: "desktop", use: { ...devices["Desktop Chrome"] } },
15
- { name: "mobile", use: { ...devices["Pixel 5"] } },
16
- ],
17
18
  webServer: {
18
19
  command: "next build && next start --hostname 127.0.0.1 --port 3100",
19
20
  url: "http://127.0.0.1:3100/en",
20
- reuseExistingServer: !process.env.CI,
21
21
  env: {
22
- AUTH_DISABLED: "true",
23
22
  NEXT_PUBLIC_APP_URL: "http://127.0.0.1:3100",
23
+ AUTH_URL: "http://127.0.0.1:3100",
24
+ NEXTAUTH_URL: "http://127.0.0.1:3100",
25
+ AUTH_SECRET: "",
26
+ AUTH_GOOGLE_ID: "",
27
+ AUTH_GOOGLE_SECRET: "",
28
+ AUTH_DISABLED: "true",
24
29
  },
30
+ reuseExistingServer: !process.env.CI,
31
+ timeout: 120_000,
25
32
  },
33
+ projects: [
34
+ {
35
+ name: "desktop",
36
+ use: {
37
+ ...devices["Desktop Chrome"],
38
+ contextOptions: { reducedMotion: "reduce" },
39
+ },
40
+ },
41
+ {
42
+ name: "mobile",
43
+ use: {
44
+ ...devices["Pixel 5"],
45
+ contextOptions: { reducedMotion: "reduce" },
46
+ },
47
+ },
48
+ ],
26
49
  });
@@ -3,3 +3,5 @@ allowBuilds:
3
3
  "@swc/core": true
4
4
  sharp: true
5
5
  unrs-resolver: true
6
+ overrides:
7
+ postcss: 8.5.15
@@ -0,0 +1,4 @@
1
+ import { resolvePackageManager, runPackageManager } from "./package-manager.ts";
2
+
3
+ const manager = resolvePackageManager();
4
+ await runPackageManager(manager, ["audit"]);
@@ -0,0 +1,68 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ export const packageManagers = ["bun", "npm", "pnpm"] as const;
4
+
5
+ export type PackageManager = (typeof packageManagers)[number];
6
+
7
+ function isPackageManager(value: string): value is PackageManager {
8
+ return packageManagers.includes(value as PackageManager);
9
+ }
10
+
11
+ export function resolvePackageManager(
12
+ env: Readonly<Record<string, string | undefined>> = process.env,
13
+ ): PackageManager {
14
+ const explicit = env.CNP_PACKAGE_MANAGER?.trim();
15
+ if (explicit) {
16
+ if (isPackageManager(explicit)) return explicit;
17
+ throw new Error(
18
+ `Unsupported package manager "${explicit}". Expected bun, npm, or pnpm.`,
19
+ );
20
+ }
21
+
22
+ const userAgent = env.npm_config_user_agent?.trim().split(/[\s/]/, 1)[0];
23
+ if (userAgent && isPackageManager(userAgent)) return userAgent;
24
+
25
+ throw new Error(
26
+ "Unable to detect the package manager. Run the script through bun, npm, or pnpm, or set CNP_PACKAGE_MANAGER.",
27
+ );
28
+ }
29
+
30
+ export function packageManagerExecutable(
31
+ manager: PackageManager,
32
+ platform: NodeJS.Platform = process.platform,
33
+ ): string {
34
+ return platform === "win32" && manager !== "bun" ? `${manager}.cmd` : manager;
35
+ }
36
+
37
+ export function installArguments(manager: PackageManager): string[] {
38
+ if (manager === "bun") return ["install", "--frozen-lockfile"];
39
+ if (manager === "pnpm") return ["install", "--no-frozen-lockfile"];
40
+ return ["install"];
41
+ }
42
+
43
+ export function runPackageManager(
44
+ manager: PackageManager,
45
+ args: string[],
46
+ options: { cwd?: string; env?: NodeJS.ProcessEnv } = {},
47
+ ): Promise<void> {
48
+ return new Promise((resolve, reject) => {
49
+ const executable = packageManagerExecutable(manager);
50
+ const child = spawn(executable, args, {
51
+ cwd: options.cwd,
52
+ env: options.env ?? process.env,
53
+ shell: false,
54
+ stdio: "inherit",
55
+ });
56
+ child.once("error", reject);
57
+ child.once("exit", (code, signal) => {
58
+ if (code === 0) resolve();
59
+ else {
60
+ reject(
61
+ new Error(
62
+ `${manager} ${args.join(" ")} failed${signal ? ` with signal ${signal}` : ` with exit code ${code ?? "unknown"}`}.`,
63
+ ),
64
+ );
65
+ }
66
+ });
67
+ });
68
+ }
@@ -1,6 +1,6 @@
1
1
  // src/app/[locale]/(public)/_home/page.tsx
2
- import HomePageUI from "@/ui/_home/page-ui";
2
+ import HomePageShell from "@/ui/_home/page-shell";
3
3
 
4
4
  export default function HomePage() {
5
- return <HomePageUI />;
5
+ return <HomePageShell />;
6
6
  }
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
2
  import GlobalHeader from "@/ui/_global/GlobalHeader";
3
- import GlobalMain from "@/ui/_global/GlobalMain";
4
3
 
5
4
  export default function PublicLayout({
6
5
  children,
@@ -10,7 +9,7 @@ export default function PublicLayout({
10
9
  return (
11
10
  <>
12
11
  <GlobalHeader hasSessionInitial={false} />
13
- <GlobalMain>{children}</GlobalMain>
12
+ {children}
14
13
  </>
15
14
  );
16
15
  }
@@ -1,8 +1,13 @@
1
1
  // src/app/[locale]/(public)/login/page.tsx
2
2
  import LoginPageUI from "@/ui/login/page-ui";
3
+ import GlobalMain from "@/ui/_global/GlobalMain";
3
4
 
4
5
  export const dynamic = "force-static";
5
6
 
6
7
  export default function LoginPage() {
7
- return <LoginPageUI />;
8
+ return (
9
+ <GlobalMain>
10
+ <LoginPageUI />
11
+ </GlobalMain>
12
+ );
8
13
  }
@@ -1,8 +1,13 @@
1
1
  // src/app/[locale]/(public)/register/page.tsx
2
2
  import RegisterPageUI from "@/ui/register/page-ui";
3
+ import GlobalMain from "@/ui/_global/GlobalMain";
3
4
 
4
5
  export const dynamic = "force-static";
5
6
 
6
7
  export default function RegisterPage() {
7
- return <RegisterPageUI />;
8
+ return (
9
+ <GlobalMain>
10
+ <RegisterPageUI />
11
+ </GlobalMain>
12
+ );
8
13
  }
@@ -1,5 +1,8 @@
1
+ import { auth } from "@/auth";
1
2
  import UserInfoPageUI from "@/ui/userInfo/page-ui";
2
3
 
3
- export default function UserInfoPage() {
4
- return <UserInfoPageUI />;
4
+ export default async function UserInfoPage() {
5
+ const session = await auth();
6
+
7
+ return <UserInfoPageUI user={session?.user} />;
5
8
  }
@@ -1,15 +1,12 @@
1
1
  import React from "react";
2
2
  import HomePage from "@/app/[locale]/(public)/_home/page";
3
3
  import GlobalHeader from "@/ui/_global/GlobalHeader";
4
- import GlobalMain from "@/ui/_global/GlobalMain";
5
4
 
6
5
  export default async function FallbackPage() {
7
6
  return (
8
7
  <>
9
8
  <GlobalHeader hasSessionInitial={false} />
10
- <GlobalMain>
11
- <HomePage />
12
- </GlobalMain>
9
+ <HomePage />
13
10
  </>
14
11
  );
15
12
  }
@@ -0,0 +1,68 @@
1
+ export const GITHUB_REPOSITORY_URL =
2
+ "https://github.com/Rising-Corporation/create-next-pro-cli";
3
+ export const GITHUB_README_URL = `${GITHUB_REPOSITORY_URL}#readme`;
4
+
5
+ const GITHUB_REPOSITORY_API_URL =
6
+ "https://api.github.com/repos/Rising-Corporation/create-next-pro-cli";
7
+
8
+ export type GitHubRepositoryStats = {
9
+ stars: number | null;
10
+ watchers: number | null;
11
+ };
12
+
13
+ const unavailableStats: GitHubRepositoryStats = {
14
+ stars: null,
15
+ watchers: null,
16
+ };
17
+
18
+ type GitHubRepositoryFetcher = (
19
+ url: string,
20
+ init: {
21
+ headers: Record<string, string>;
22
+ next: { revalidate: number };
23
+ },
24
+ ) => Promise<Response>;
25
+
26
+ export function parseGitHubRepositoryStats(
27
+ value: unknown,
28
+ ): GitHubRepositoryStats {
29
+ if (!value || typeof value !== "object") return unavailableStats;
30
+
31
+ const repository = value as Record<string, unknown>;
32
+ const stars = repository.stargazers_count;
33
+ const watchers = repository.subscribers_count;
34
+
35
+ if (
36
+ !Number.isInteger(stars) ||
37
+ (stars as number) < 0 ||
38
+ !Number.isInteger(watchers) ||
39
+ (watchers as number) < 0
40
+ ) {
41
+ return unavailableStats;
42
+ }
43
+
44
+ return {
45
+ stars: stars as number,
46
+ watchers: watchers as number,
47
+ };
48
+ }
49
+
50
+ export async function getGitHubRepositoryStats(
51
+ fetcher: GitHubRepositoryFetcher = fetch,
52
+ ): Promise<GitHubRepositoryStats> {
53
+ try {
54
+ const response = await fetcher(GITHUB_REPOSITORY_API_URL, {
55
+ headers: {
56
+ Accept: "application/vnd.github+json",
57
+ "X-GitHub-Api-Version": "2022-11-28",
58
+ },
59
+ next: { revalidate: 3600 },
60
+ });
61
+
62
+ if (!response.ok) return unavailableStats;
63
+
64
+ return parseGitHubRepositoryStats(await response.json());
65
+ } catch {
66
+ return unavailableStats;
67
+ }
68
+ }
@@ -2,14 +2,19 @@ import React from "react";
2
2
 
3
3
  export default function GlobalMain({
4
4
  children,
5
+ footer,
5
6
  }: {
6
7
  children: React.ReactNode;
8
+ footer?: React.ReactNode;
7
9
  }) {
8
10
  return (
9
- <main className="mt-0 flex min-h-screen flex-col bg-neutral-50 text-foreground font-sans hero-pattern">
10
- <div className="flex-1 flex flex-col justify-center items-stretch mx-auto w-full px-6 self-stretch">
11
- {children}
12
- </div>
13
- </main>
11
+ <div className="mt-0 flex min-h-screen flex-col bg-neutral-50 text-foreground font-sans hero-pattern">
12
+ <main className="flex flex-1 flex-col">
13
+ <div className="flex-1 flex flex-col justify-center items-stretch mx-auto w-full px-6 self-stretch">
14
+ {children}
15
+ </div>
16
+ </main>
17
+ {footer}
18
+ </div>
14
19
  );
15
20
  }
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- import { useSyncExternalStore } from "react";
2
+ import { useEffect, useSyncExternalStore } from "react";
3
3
  import { Moon, Sun } from "lucide-react";
4
4
  import { Button } from "./Button";
5
- import { useTranslations } from "next-intl";
5
+ import { useLocale, useTranslations } from "next-intl";
6
6
 
7
7
  const themeChangeEvent = "template-theme-change";
8
8
 
@@ -24,8 +24,16 @@ function subscribeToThemeChange(onStoreChange: () => void) {
24
24
  };
25
25
  }
26
26
 
27
+ function applyTheme(dark: boolean) {
28
+ document.documentElement.classList.toggle("dark", dark);
29
+ document.documentElement.classList.toggle("light", !dark);
30
+ localStorage.setItem("theme", dark ? "dark" : "light");
31
+ window.dispatchEvent(new Event(themeChangeEvent));
32
+ }
33
+
27
34
  export default function ThemeToggle() {
28
35
  const t = useTranslations("_global_ui");
36
+ const locale = useLocale();
29
37
  const theme = useSyncExternalStore(
30
38
  subscribeToThemeChange,
31
39
  getThemeSnapshot,
@@ -33,12 +41,14 @@ export default function ThemeToggle() {
33
41
  );
34
42
  const isDark = theme === "dark";
35
43
 
36
- const applyTheme = (dark: boolean) => {
37
- document.documentElement.classList.toggle("dark", dark);
38
- document.documentElement.classList.toggle("light", !dark);
39
- localStorage.setItem("theme", dark ? "dark" : "light");
40
- window.dispatchEvent(new Event(themeChangeEvent));
41
- };
44
+ useEffect(() => {
45
+ const storedTheme = localStorage.getItem("theme");
46
+ const shouldUseDark = storedTheme
47
+ ? storedTheme === "dark"
48
+ : window.matchMedia("(prefers-color-scheme: dark)").matches;
49
+
50
+ applyTheme(shouldUseDark);
51
+ }, [locale]);
42
52
 
43
53
  return (
44
54
  <Button
@@ -2,6 +2,7 @@
2
2
  import { Link } from "@/lib/i18n/navigation";
3
3
  import { useTranslations } from "next-intl";
4
4
  import LogoutButton from "../dashboard/LogoutButton";
5
+ import ThemeToggle from "./ThemeToggle";
5
6
 
6
7
  const navLinks = [
7
8
  { href: "/dashboard", labelKey: "user_nav.links.dashboard" },
@@ -18,12 +19,15 @@ const UserNav = () => {
18
19
  <li key={link.href}>
19
20
  <Link
20
21
  href={link.href}
21
- className="inline-block rounded px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-stone-600 hover:bg-stone-50 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-stone-500"
22
+ className="inline-block rounded px-3 py-1.5 text-sm font-medium dark:text-stone-100 light:text-stone-900 hover:text-stone-600 hover:bg-stone-50 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-stone-500"
22
23
  >
23
24
  {t(link.labelKey)}
24
25
  </Link>
25
26
  </li>
26
27
  ))}
28
+ <li>
29
+ <ThemeToggle />
30
+ </li>
27
31
  <li>
28
32
  <LogoutButton />
29
33
  </li>
@@ -0,0 +1,57 @@
1
+ import { Eye, Star } from "lucide-react";
2
+ import { getTranslations } from "next-intl/server";
3
+ import { Button } from "@/ui/_global/Button";
4
+ import {
5
+ GITHUB_REPOSITORY_URL,
6
+ getGitHubRepositoryStats,
7
+ } from "@/lib/github/repository";
8
+
9
+ export default async function GitHubActions() {
10
+ const [t, stats] = await Promise.all([
11
+ getTranslations("_home.github"),
12
+ getGitHubRepositoryStats(),
13
+ ]);
14
+
15
+ const actions = [
16
+ {
17
+ label: t("star"),
18
+ count: stats.stars,
19
+ icon: Star,
20
+ },
21
+ {
22
+ label: t("watch"),
23
+ count: stats.watchers,
24
+ icon: Eye,
25
+ },
26
+ ];
27
+
28
+ return (
29
+ <div className="mt-12 text-center">
30
+ <p className="mb-4">{t("project_credit")}</p>
31
+ <div className="flex flex-wrap items-center justify-center gap-4">
32
+ {actions.map(({ label, count, icon: Icon }) => (
33
+ <Button key={label} asChild variant="outline">
34
+ <a
35
+ href={GITHUB_REPOSITORY_URL}
36
+ target="_blank"
37
+ rel="noreferrer"
38
+ aria-label={
39
+ count === null
40
+ ? label
41
+ : t("action_with_count", { label, count })
42
+ }
43
+ >
44
+ <Icon className="mr-2 h-4 w-4" aria-hidden="true" />
45
+ <span>{label}</span>
46
+ {count !== null && (
47
+ <span className="ml-2 border-l border-current pl-2 tabular-nums">
48
+ {count}
49
+ </span>
50
+ )}
51
+ </a>
52
+ </Button>
53
+ ))}
54
+ </div>
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,16 @@
1
+ import GlobalMain from "@/ui/_global/GlobalMain";
2
+ import HomePageUI from "@/ui/_home/page-ui";
3
+
4
+ const footer = (
5
+ <footer className="border-t px-4 py-6 text-center text-sm text-gray-600 dark:text-gray-300">
6
+ <p>Empowered by Rising Corporation</p>
7
+ </footer>
8
+ );
9
+
10
+ export default function HomePageShell() {
11
+ return (
12
+ <GlobalMain footer={footer}>
13
+ <HomePageUI />
14
+ </GlobalMain>
15
+ );
16
+ }
@@ -1,25 +1,36 @@
1
1
  // src/ui/_home/page-ui.tsx
2
2
  import { getTranslations } from "next-intl/server";
3
+ import { ArrowRight } from "lucide-react";
4
+ import { GITHUB_README_URL } from "@/lib/github/repository";
5
+ import GitHubActions from "@/ui/_home/GitHubActions";
3
6
 
4
7
  export default async function HomePageUI() {
5
8
  const t = await getTranslations("_home");
6
9
 
7
10
  return (
8
- <>
9
- <section className="pt-32 pb-20 px-4 sm:px-6 lg:px-8">
10
- <div className="max-w-7xl mx-auto">
11
- <div className="grid lg:grid-cols-2 gap-12 items-center">
12
- <h1 className="text-5xl lg:text-6xl font-bold mb-6 leading-tight">
13
- {t.rich("title", {
14
- span: (chunks) => (
15
- <span className="gradient-text">{chunks}</span>
16
- ),
17
- })}
18
- </h1>
11
+ <section className="pt-32 pb-20 px-4 sm:px-6 lg:px-8">
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) => <span className="gradient-text">{chunks}</span>,
17
+ })}
18
+ </h1>
19
+ <div>
19
20
  <p className="text-lg">{t("description")}</p>
21
+ <a
22
+ href={GITHUB_README_URL}
23
+ target="_blank"
24
+ rel="noreferrer"
25
+ className="mt-4 inline-flex items-center gap-2 font-medium underline underline-offset-4 hover:text-stone-600"
26
+ >
27
+ {t("readme")}
28
+ <ArrowRight className="h-4 w-4" aria-hidden="true" />
29
+ </a>
20
30
  </div>
21
31
  </div>
22
- </section>
23
- </>
32
+ <GitHubActions />
33
+ </div>
34
+ </section>
24
35
  );
25
36
  }
@@ -19,7 +19,7 @@ const LogoutButton = () => {
19
19
  variant="ghost"
20
20
  size="sm"
21
21
  onClick={handleLogout}
22
- className="text-gray-400 hover:text-black glass-effect"
22
+ className="text-gray-400 hover:text-black dark:hover:text-white glass-effect"
23
23
  >
24
24
  <LogOut className="h-4 w-4 sm:mr-2" />
25
25
  <span className="hidden sm:inline">{t("logout")}</span>
@@ -2,9 +2,9 @@ import React from "react";
2
2
 
3
3
  export default function StatsCard({ t }: { t: (key: string) => string }) {
4
4
  return (
5
- <div className="rounded border bg-white p-6 shadow-sm">
5
+ <div className="rounded border bg-white p-6 shadow-sm dark:bg-black">
6
6
  <h2 className="text-lg font-semibold mb-2">{t("widgets.stats.title")}</h2>
7
- <ul className="text-sm text-gray-600 space-y-1">
7
+ <ul className="text-sm text-gray-600 space-y-1 dark:text-gray-300">
8
8
  <li>{t("widgets.stats.views")}: 123</li>
9
9
  <li>{t("widgets.stats.likes")}: 45</li>
10
10
  <li>{t("widgets.stats.comments")}: 7</li>