create-next-structure 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.
- package/LICENSE +7 -0
- package/README.md +51 -0
- package/bin/index.js +90 -0
- package/package.json +44 -0
- package/templates/.env.template +36 -0
- package/templates/app/(dashboard)/dashboard/page.jsx +40 -0
- package/templates/app/(dashboard)/layout.jsx +24 -0
- package/templates/app/(dashboard)/users/page.jsx +64 -0
- package/templates/app/globals.css +14 -0
- package/templates/app/layout.jsx +25 -0
- package/templates/app/login/page.jsx +69 -0
- package/templates/app/page.jsx +10 -0
- package/templates/components/auth/withAuth.jsx +50 -0
- package/templates/components/auth/withPublic.jsx +50 -0
- package/templates/components/layout/Header.jsx +29 -0
- package/templates/components/layout/Sidebar.jsx +35 -0
- package/templates/components/ui/Button.jsx +36 -0
- package/templates/components/ui/ErrorDisplay.jsx +19 -0
- package/templates/components/ui/Input.jsx +30 -0
- package/templates/components/ui/Loading.jsx +16 -0
- package/templates/components/ui/Modal.jsx +33 -0
- package/templates/components/ui/index.js +10 -0
- package/templates/contexts/AuthContext.jsx +112 -0
- package/templates/docs/README.md +128 -0
- package/templates/hooks/useAsync.js +38 -0
- package/templates/hooks/useAuth.js +93 -0
- package/templates/hooks/useForm.js +67 -0
- package/templates/jsconfig.json +27 -0
- package/templates/lib/api/apiClient.js +105 -0
- package/templates/lib/api/auth.api.js +36 -0
- package/templates/lib/api/user.api.js +43 -0
- package/templates/next.config.js +18 -0
- package/templates/package.json +23 -0
- package/templates/store/ReduxProvider.jsx +34 -0
- package/templates/store/api/apiSlice.js +108 -0
- package/templates/store/api/authApi.js +155 -0
- package/templates/store/api/exampleApi.js +108 -0
- package/templates/store/api/userApi.js +114 -0
- package/templates/store/slices/authSlice.js +86 -0
- package/templates/store/store.js +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MD. Sifat Islam
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
# create-next-structure
|
|
3
|
+
|
|
4
|
+
CLI tool to scaffold a **Next.js + Redux Toolkit** project structure with auth, RTK Query, and a ready-to-use template.
|
|
5
|
+
|
|
6
|
+
## ✅ Features
|
|
7
|
+
|
|
8
|
+
- Next.js App Router layout
|
|
9
|
+
- Redux Toolkit + RTK Query pre-configured
|
|
10
|
+
- Auth flow helpers and protected routes
|
|
11
|
+
- Reusable UI components and layout shell
|
|
12
|
+
- Template documentation included
|
|
13
|
+
|
|
14
|
+
## 📦 Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g create-next-structure
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 🚀 Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
create-next-structure my-app
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd my-app
|
|
30
|
+
npm install
|
|
31
|
+
npm run dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 🧩 Template docs
|
|
35
|
+
|
|
36
|
+
After generation, read:
|
|
37
|
+
|
|
38
|
+
- `docs/README.md` (template usage overview)
|
|
39
|
+
- `docs/PROJECT_STRUCTURE.md`
|
|
40
|
+
- `docs/API_INTEGRATION.md`
|
|
41
|
+
- `docs/ARCHITECTURE.md`
|
|
42
|
+
- `docs/EXAMPLES.md`
|
|
43
|
+
|
|
44
|
+
## 🔧 Notes
|
|
45
|
+
|
|
46
|
+
- Make sure you create `.env.local` using `.env.template` and set `NEXT_PUBLIC_API_URL`.
|
|
47
|
+
- Protected routes live under `app/(dashboard)`.
|
|
48
|
+
|
|
49
|
+
## 📄 License
|
|
50
|
+
|
|
51
|
+
ISC
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
// Update notifier
|
|
9
|
+
import updateNotifier from "update-notifier";
|
|
10
|
+
import pkg from "../package.json" assert { type: "json" };
|
|
11
|
+
|
|
12
|
+
const notifier = updateNotifier({
|
|
13
|
+
pkg,
|
|
14
|
+
updateCheckInterval: 1000 * 60 * 60, // 1 hour
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
notifier.notify();
|
|
18
|
+
|
|
19
|
+
if (notifier.update) {
|
|
20
|
+
console.log(
|
|
21
|
+
chalk.yellow(`Update available ${pkg.version} → ${notifier.update.latest}`),
|
|
22
|
+
);
|
|
23
|
+
console.log(chalk.blue(`Run npm install -g ${pkg.name}@latest to update.\n`));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
27
|
+
const __dirname = path.dirname(__filename);
|
|
28
|
+
|
|
29
|
+
// project name --> from user arg
|
|
30
|
+
const projectName = (process.argv[2] || "").trim();
|
|
31
|
+
|
|
32
|
+
// check if project name is provided
|
|
33
|
+
if (!projectName) {
|
|
34
|
+
console.log(chalk.red("Please provide a project name"));
|
|
35
|
+
console.log(chalk.blue("Usage: create-next-structure <project-name>"));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// basic validation for folder name
|
|
40
|
+
const invalidNameChars = /[<>:"/\\|?*\x00-\x1F]/;
|
|
41
|
+
if (invalidNameChars.test(projectName)) {
|
|
42
|
+
console.log(chalk.red("Invalid project name. Avoid special characters."));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Dirs
|
|
47
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
48
|
+
const templateDir = path.join(__dirname, "../templates");
|
|
49
|
+
|
|
50
|
+
// Create project function
|
|
51
|
+
async function createProject() {
|
|
52
|
+
const spinner = ora("Creating project structure...").start();
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Check template dir exists
|
|
56
|
+
const templateExists = await fs.pathExists(templateDir);
|
|
57
|
+
if (!templateExists) {
|
|
58
|
+
spinner.fail("Template folder not found");
|
|
59
|
+
console.error(chalk.red(`Missing template at: ${templateDir}`));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Prevent overwrite
|
|
64
|
+
const targetExists = await fs.pathExists(targetDir);
|
|
65
|
+
if (targetExists) {
|
|
66
|
+
spinner.fail("Target folder already exists");
|
|
67
|
+
console.error(chalk.red(`Folder already exists: ${targetDir}`));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// copy template files to target dir
|
|
72
|
+
await fs.copy(templateDir, targetDir);
|
|
73
|
+
|
|
74
|
+
spinner.succeed("Project structure created successfully!");
|
|
75
|
+
console.log(
|
|
76
|
+
chalk.green(`Your Next.js project "${projectName}" has been created!`),
|
|
77
|
+
);
|
|
78
|
+
console.log(
|
|
79
|
+
chalk.blue(`Navigate to the project directory: cd ${projectName}`),
|
|
80
|
+
);
|
|
81
|
+
console.log(chalk.blue("Install dependencies: npm install"));
|
|
82
|
+
console.log(chalk.blue("Start development server: npm run dev"));
|
|
83
|
+
} catch (err) {
|
|
84
|
+
spinner.fail("Failed to create project structure");
|
|
85
|
+
console.error(chalk.red(err));
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
createProject();
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-next-structure",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-next-structure": "bin/index.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"description": "Scaffold a Next.js + Redux Toolkit project structure with auth and RTK Query.",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"nextjs",
|
|
12
|
+
"redux",
|
|
13
|
+
"rtk-query",
|
|
14
|
+
"template",
|
|
15
|
+
"scaffold",
|
|
16
|
+
"cli",
|
|
17
|
+
"starter"
|
|
18
|
+
],
|
|
19
|
+
"author": "MD. Sifat Islam",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/stackmatrixdev/create-next-structure.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/stackmatrixdev/create-next-structure#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/stackmatrixdev/create-next-structure/issues"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin",
|
|
31
|
+
"templates",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"chalk": "^5.3.0",
|
|
39
|
+
"fs-extra": "^11.2.0",
|
|
40
|
+
"inquirer": "^9.2.0",
|
|
41
|
+
"ora": "^7.0.1",
|
|
42
|
+
"update-notifier": "^7.3.1"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Environment Variables Template
|
|
2
|
+
|
|
3
|
+
# Copy this file to .env.local and fill in your values
|
|
4
|
+
# .env.local is ignored by git for security
|
|
5
|
+
|
|
6
|
+
# ===========================================
|
|
7
|
+
# API Configuration
|
|
8
|
+
# ===========================================
|
|
9
|
+
|
|
10
|
+
# Backend API Base URL (REQUIRED)
|
|
11
|
+
# Development: http://localhost:5000/api/v1
|
|
12
|
+
# Production: https://your-api.com/api/v1
|
|
13
|
+
NEXT_PUBLIC_API_URL=http://localhost:5000/api/v1
|
|
14
|
+
|
|
15
|
+
# ===========================================
|
|
16
|
+
# Optional Configuration
|
|
17
|
+
# ===========================================
|
|
18
|
+
|
|
19
|
+
# App Name
|
|
20
|
+
NEXT_PUBLIC_APP_NAME="My App"
|
|
21
|
+
|
|
22
|
+
# App Version
|
|
23
|
+
NEXT_PUBLIC_APP_VERSION="1.0.0"
|
|
24
|
+
|
|
25
|
+
# Enable Debug Mode (show console logs)
|
|
26
|
+
NEXT_PUBLIC_DEBUG_MODE=false
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ===========================================
|
|
30
|
+
# Notes
|
|
31
|
+
# ===========================================
|
|
32
|
+
|
|
33
|
+
# 1. NEXT_PUBLIC_ prefix makes variables available in browser
|
|
34
|
+
# 2. Variables without prefix are server-side only
|
|
35
|
+
# 3. Never commit .env.local to version control
|
|
36
|
+
# 4. Update values based on your environment
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Page (Redux Version)
|
|
3
|
+
* Responsibility: Main dashboard view with Redux state
|
|
4
|
+
*
|
|
5
|
+
* FEATURES:
|
|
6
|
+
* - Uses Redux to access user data
|
|
7
|
+
* - Shows current user information
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use client";
|
|
11
|
+
|
|
12
|
+
import { useSelector } from "react-redux";
|
|
13
|
+
import { selectCurrentUser } from "@/store/slices/authSlice";
|
|
14
|
+
|
|
15
|
+
export default function DashboardPage() {
|
|
16
|
+
const user = useSelector(selectCurrentUser);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="dashboard">
|
|
20
|
+
<h1>Dashboard</h1>
|
|
21
|
+
<p>Welcome, {user?.email || user?.name || "User"}</p>
|
|
22
|
+
<div className="user-info">
|
|
23
|
+
{user && (
|
|
24
|
+
<>
|
|
25
|
+
<p>
|
|
26
|
+
<strong>Email:</strong> {user.email}
|
|
27
|
+
</p>
|
|
28
|
+
<p>
|
|
29
|
+
<strong>Role:</strong> {user.role || "N/A"}
|
|
30
|
+
</p>
|
|
31
|
+
<p>
|
|
32
|
+
<strong>ID:</strong> {user.id || user._id}
|
|
33
|
+
</p>
|
|
34
|
+
</>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
{/* Add dashboard content here */}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Layout
|
|
3
|
+
* Responsibility: Protected layout with header and sidebar
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import { Header } from "@/components/layout/Header";
|
|
9
|
+
import { Sidebar } from "@/components/layout/Sidebar";
|
|
10
|
+
import { withAuth } from "@/components/auth/withAuth";
|
|
11
|
+
|
|
12
|
+
function DashboardLayout({ children }) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="app-layout">
|
|
15
|
+
<Header />
|
|
16
|
+
<div className="layout-content">
|
|
17
|
+
<Sidebar />
|
|
18
|
+
<main className="main-content">{children}</main>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default withAuth(DashboardLayout);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Users Page (Redux Version with RTK Query)
|
|
3
|
+
* Responsibility: Display and manage users using RTK Query
|
|
4
|
+
*
|
|
5
|
+
* FEATURES:
|
|
6
|
+
* - Automatic data fetching with RTK Query
|
|
7
|
+
* - Built-in caching and refetching
|
|
8
|
+
* - Loading and error states handled automatically
|
|
9
|
+
* - No manual useState or useEffect needed!
|
|
10
|
+
*
|
|
11
|
+
* USAGE EXAMPLE:
|
|
12
|
+
* - Data is automatically fetched when component mounts
|
|
13
|
+
* - Refetch manually: refetch()
|
|
14
|
+
* - Access loading state: isLoading
|
|
15
|
+
* - Access error state: error
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
"use client";
|
|
19
|
+
|
|
20
|
+
import { useGetUsersQuery } from "@/store/api/userApi";
|
|
21
|
+
import { Loading, ErrorDisplay } from "@/components/ui";
|
|
22
|
+
|
|
23
|
+
export default function UsersPage() {
|
|
24
|
+
// RTK Query automatically handles fetching, caching, and state
|
|
25
|
+
const { data, isLoading, error, refetch } = useGetUsersQuery({
|
|
26
|
+
page: 1,
|
|
27
|
+
limit: 10,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (isLoading) return <Loading />;
|
|
31
|
+
if (error)
|
|
32
|
+
return (
|
|
33
|
+
<ErrorDisplay
|
|
34
|
+
error={error.data?.message || "Failed to load users"}
|
|
35
|
+
onRetry={refetch}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const users = data?.users || data?.data || [];
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="users-page">
|
|
43
|
+
<div className="users-header">
|
|
44
|
+
<h1>Users</h1>
|
|
45
|
+
<button onClick={refetch}>Refresh</button>
|
|
46
|
+
</div>
|
|
47
|
+
<div className="users-list">
|
|
48
|
+
{users.length === 0 ? (
|
|
49
|
+
<p>No users found</p>
|
|
50
|
+
) : (
|
|
51
|
+
users.map((user) => (
|
|
52
|
+
<div key={user._id || user.id} className="user-card">
|
|
53
|
+
<p>
|
|
54
|
+
<strong>{user.name || user.email}</strong>
|
|
55
|
+
</p>
|
|
56
|
+
<p>{user.email}</p>
|
|
57
|
+
<span>{user.role || "User"}</span>
|
|
58
|
+
</div>
|
|
59
|
+
))
|
|
60
|
+
)}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Styles
|
|
3
|
+
* Add your global CSS/styling framework here
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
8
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
9
|
+
sans-serif;
|
|
10
|
+
-webkit-font-smoothing: antialiased;
|
|
11
|
+
-moz-osx-font-smoothing: grayscale;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Add your component styles or import CSS framework */
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Root Layout
|
|
3
|
+
* Responsibility: Main application layout wrapper with Redux Provider
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Inter } from "next/font/google";
|
|
7
|
+
import { ReduxProvider } from "@/store/ReduxProvider";
|
|
8
|
+
import "./globals.css";
|
|
9
|
+
|
|
10
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
11
|
+
|
|
12
|
+
export const metadata = {
|
|
13
|
+
title: "App Template - Redux",
|
|
14
|
+
description: "Next.js starter template with Redux Toolkit & RTK Query",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default function RootLayout({ children }) {
|
|
18
|
+
return (
|
|
19
|
+
<html lang="en">
|
|
20
|
+
<body className={inter.className}>
|
|
21
|
+
<ReduxProvider>{children}</ReduxProvider>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login Page (Redux Version)
|
|
3
|
+
* Responsibility: User login form with Redux & RTK Query
|
|
4
|
+
*
|
|
5
|
+
* FEATURES:
|
|
6
|
+
* - Uses Redux for state management
|
|
7
|
+
* - Uses RTK Query for API calls
|
|
8
|
+
* - Auto-redirects on successful login
|
|
9
|
+
* - Protected with withPublic HOC
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use client";
|
|
13
|
+
|
|
14
|
+
import { useState } from "react";
|
|
15
|
+
import { useAuth } from "@/hooks/useAuth";
|
|
16
|
+
import { withPublic } from "@/components/auth/withPublic";
|
|
17
|
+
import { Button, Input } from "@/components/ui";
|
|
18
|
+
|
|
19
|
+
function LoginPage() {
|
|
20
|
+
const { login, isLoading } = useAuth();
|
|
21
|
+
const [formData, setFormData] = useState({ email: "", password: "" });
|
|
22
|
+
const [error, setError] = useState("");
|
|
23
|
+
|
|
24
|
+
const handleSubmit = async (e) => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
setError("");
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await login(formData);
|
|
30
|
+
// Auto-redirects to /dashboard on success
|
|
31
|
+
} catch (err) {
|
|
32
|
+
setError(err?.data?.message || "Login failed. Please try again.");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="auth-page">
|
|
38
|
+
<div className="auth-card">
|
|
39
|
+
<h1>Login</h1>
|
|
40
|
+
<form onSubmit={handleSubmit}>
|
|
41
|
+
<Input
|
|
42
|
+
label="Email"
|
|
43
|
+
type="email"
|
|
44
|
+
value={formData.email}
|
|
45
|
+
onChange={(e) =>
|
|
46
|
+
setFormData({ ...formData, email: e.target.value })
|
|
47
|
+
}
|
|
48
|
+
required
|
|
49
|
+
/>
|
|
50
|
+
<Input
|
|
51
|
+
label="Password"
|
|
52
|
+
type="password"
|
|
53
|
+
value={formData.password}
|
|
54
|
+
onChange={(e) =>
|
|
55
|
+
setFormData({ ...formData, password: e.target.value })
|
|
56
|
+
}
|
|
57
|
+
required
|
|
58
|
+
/>
|
|
59
|
+
{error && <div className="error">{error}</div>}
|
|
60
|
+
<Button type="submit" loading={isLoading}>
|
|
61
|
+
Login
|
|
62
|
+
</Button>
|
|
63
|
+
</form>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default withPublic(LoginPage);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protected Route HOC (Redux Version)
|
|
3
|
+
* Responsibility: Restrict access to authenticated users only
|
|
4
|
+
*
|
|
5
|
+
* USAGE GUIDE:
|
|
6
|
+
* - Wrap any component that requires authentication
|
|
7
|
+
* - Automatically redirects to login if user is not authenticated
|
|
8
|
+
*
|
|
9
|
+
* EXAMPLE:
|
|
10
|
+
* ```jsx
|
|
11
|
+
* import { withAuth } from '@/components/auth/withAuth';
|
|
12
|
+
*
|
|
13
|
+
* function DashboardPage() {
|
|
14
|
+
* return <div>Protected Dashboard</div>;
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* export default withAuth(DashboardPage);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
"use client";
|
|
22
|
+
|
|
23
|
+
import { useEffect } from "react";
|
|
24
|
+
import { useRouter } from "next/navigation";
|
|
25
|
+
import { useSelector } from "react-redux";
|
|
26
|
+
import { selectIsAuthenticated, selectToken } from "@/store/slices/authSlice";
|
|
27
|
+
import Loading from "@/components/ui/Loading";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Higher-order component that protects routes requiring authentication
|
|
31
|
+
*/
|
|
32
|
+
export const withAuth = (Component) => {
|
|
33
|
+
return function ProtectedRoute(props) {
|
|
34
|
+
const router = useRouter();
|
|
35
|
+
const isAuthenticated = useSelector(selectIsAuthenticated);
|
|
36
|
+
const token = useSelector(selectToken);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!isAuthenticated && !token) {
|
|
40
|
+
router.push("/login");
|
|
41
|
+
}
|
|
42
|
+
}, [isAuthenticated, token, router]);
|
|
43
|
+
|
|
44
|
+
if (!isAuthenticated || !token) {
|
|
45
|
+
return <Loading />;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return <Component {...props} />;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Route HOC (Redux Version)
|
|
3
|
+
* Responsibility: Restrict access to non-authenticated users only
|
|
4
|
+
*
|
|
5
|
+
* USAGE GUIDE:
|
|
6
|
+
* - Wrap any component that should only be accessible to non-authenticated users
|
|
7
|
+
* - Automatically redirects to dashboard if user is authenticated
|
|
8
|
+
* - Perfect for login, register, forgot password pages
|
|
9
|
+
*
|
|
10
|
+
* EXAMPLE:
|
|
11
|
+
* ```jsx
|
|
12
|
+
* import { withPublic } from '@/components/auth/withPublic';
|
|
13
|
+
*
|
|
14
|
+
* function LoginPage() {
|
|
15
|
+
* return <div>Login Form</div>;
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* export default withPublic(LoginPage);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
"use client";
|
|
23
|
+
|
|
24
|
+
import { useEffect } from "react";
|
|
25
|
+
import { useRouter } from "next/navigation";
|
|
26
|
+
import { useSelector } from "react-redux";
|
|
27
|
+
import { selectIsAuthenticated } from "@/store/slices/authSlice";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Higher-order component that protects routes for non-authenticated users
|
|
31
|
+
* Redirects to dashboard if user is already logged in
|
|
32
|
+
*/
|
|
33
|
+
export const withPublic = (Component, redirectTo = "/dashboard") => {
|
|
34
|
+
return function PublicRoute(props) {
|
|
35
|
+
const router = useRouter();
|
|
36
|
+
const isAuthenticated = useSelector(selectIsAuthenticated);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (isAuthenticated) {
|
|
40
|
+
router.push(redirectTo);
|
|
41
|
+
}
|
|
42
|
+
}, [isAuthenticated, router]);
|
|
43
|
+
|
|
44
|
+
if (isAuthenticated) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return <Component {...props} />;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header Component
|
|
3
|
+
* Responsibility: App header with user menu
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import { useAuth } from "@/contexts/AuthContext";
|
|
9
|
+
|
|
10
|
+
export const Header = () => {
|
|
11
|
+
const { user, logout } = useAuth();
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<header className="app-header">
|
|
15
|
+
<div className="header-content">
|
|
16
|
+
<h1 className="app-title">App Name</h1>
|
|
17
|
+
|
|
18
|
+
<nav className="header-nav">
|
|
19
|
+
{user && (
|
|
20
|
+
<div className="user-menu">
|
|
21
|
+
<span>{user.email}</span>
|
|
22
|
+
<button onClick={logout}>Logout</button>
|
|
23
|
+
</div>
|
|
24
|
+
)}
|
|
25
|
+
</nav>
|
|
26
|
+
</div>
|
|
27
|
+
</header>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar Component
|
|
3
|
+
* Responsibility: Navigation sidebar
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
"use client";
|
|
7
|
+
|
|
8
|
+
import Link from "next/link";
|
|
9
|
+
import { usePathname } from "next/navigation";
|
|
10
|
+
|
|
11
|
+
export const Sidebar = () => {
|
|
12
|
+
const pathname = usePathname();
|
|
13
|
+
|
|
14
|
+
const navItems = [
|
|
15
|
+
{ path: "/dashboard", label: "Dashboard" },
|
|
16
|
+
{ path: "/users", label: "Users" },
|
|
17
|
+
// Add more navigation items here
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<aside className="sidebar">
|
|
22
|
+
<nav className="sidebar-nav">
|
|
23
|
+
{navItems.map((item) => (
|
|
24
|
+
<Link
|
|
25
|
+
key={item.path}
|
|
26
|
+
href={item.path}
|
|
27
|
+
className={`nav-item ${pathname === item.path ? "active" : ""}`}
|
|
28
|
+
>
|
|
29
|
+
{item.label}
|
|
30
|
+
</Link>
|
|
31
|
+
))}
|
|
32
|
+
</nav>
|
|
33
|
+
</aside>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Component
|
|
3
|
+
* Responsibility: Reusable button with consistent styling and variants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Base button component
|
|
8
|
+
* @param {string} variant - Button style variant: 'primary', 'secondary', 'danger'
|
|
9
|
+
* @param {string} size - Button size: 'sm', 'md', 'lg'
|
|
10
|
+
* @param {boolean} disabled - Disabled state
|
|
11
|
+
* @param {boolean} loading - Loading state
|
|
12
|
+
* @param {function} onClick - Click handler
|
|
13
|
+
*/
|
|
14
|
+
export const Button = ({
|
|
15
|
+
children,
|
|
16
|
+
variant = "primary",
|
|
17
|
+
size = "md",
|
|
18
|
+
disabled = false,
|
|
19
|
+
loading = false,
|
|
20
|
+
onClick,
|
|
21
|
+
type = "button",
|
|
22
|
+
className = "",
|
|
23
|
+
...props
|
|
24
|
+
}) => {
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
type={type}
|
|
28
|
+
onClick={onClick}
|
|
29
|
+
disabled={disabled || loading}
|
|
30
|
+
className={`btn btn-${variant} btn-${size} ${className}`}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{loading ? "Loading..." : children}
|
|
34
|
+
</button>
|
|
35
|
+
);
|
|
36
|
+
};
|