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.
Files changed (40) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +51 -0
  3. package/bin/index.js +90 -0
  4. package/package.json +44 -0
  5. package/templates/.env.template +36 -0
  6. package/templates/app/(dashboard)/dashboard/page.jsx +40 -0
  7. package/templates/app/(dashboard)/layout.jsx +24 -0
  8. package/templates/app/(dashboard)/users/page.jsx +64 -0
  9. package/templates/app/globals.css +14 -0
  10. package/templates/app/layout.jsx +25 -0
  11. package/templates/app/login/page.jsx +69 -0
  12. package/templates/app/page.jsx +10 -0
  13. package/templates/components/auth/withAuth.jsx +50 -0
  14. package/templates/components/auth/withPublic.jsx +50 -0
  15. package/templates/components/layout/Header.jsx +29 -0
  16. package/templates/components/layout/Sidebar.jsx +35 -0
  17. package/templates/components/ui/Button.jsx +36 -0
  18. package/templates/components/ui/ErrorDisplay.jsx +19 -0
  19. package/templates/components/ui/Input.jsx +30 -0
  20. package/templates/components/ui/Loading.jsx +16 -0
  21. package/templates/components/ui/Modal.jsx +33 -0
  22. package/templates/components/ui/index.js +10 -0
  23. package/templates/contexts/AuthContext.jsx +112 -0
  24. package/templates/docs/README.md +128 -0
  25. package/templates/hooks/useAsync.js +38 -0
  26. package/templates/hooks/useAuth.js +93 -0
  27. package/templates/hooks/useForm.js +67 -0
  28. package/templates/jsconfig.json +27 -0
  29. package/templates/lib/api/apiClient.js +105 -0
  30. package/templates/lib/api/auth.api.js +36 -0
  31. package/templates/lib/api/user.api.js +43 -0
  32. package/templates/next.config.js +18 -0
  33. package/templates/package.json +23 -0
  34. package/templates/store/ReduxProvider.jsx +34 -0
  35. package/templates/store/api/apiSlice.js +108 -0
  36. package/templates/store/api/authApi.js +155 -0
  37. package/templates/store/api/exampleApi.js +108 -0
  38. package/templates/store/api/userApi.js +114 -0
  39. package/templates/store/slices/authSlice.js +86 -0
  40. package/templates/store/store.js +27 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Error Component
3
+ * Responsibility: Display error state with retry option
4
+ */
5
+
6
+ import { Button } from "./Button";
7
+
8
+ /**
9
+ * Error display component
10
+ */
11
+ export const ErrorDisplay = ({ error, onRetry }) => {
12
+ return (
13
+ <div className="error-container">
14
+ <h3>Something went wrong</h3>
15
+ <p>{error || "An unexpected error occurred"}</p>
16
+ {onRetry && <Button onClick={onRetry}>Try Again</Button>}
17
+ </div>
18
+ );
19
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Input Component
3
+ * Responsibility: Reusable form input with validation display
4
+ */
5
+
6
+ /**
7
+ * Base input component
8
+ * @param {string} label - Input label
9
+ * @param {string} error - Error message
10
+ * @param {string} type - Input type
11
+ */
12
+ export const Input = ({
13
+ label,
14
+ error,
15
+ type = "text",
16
+ className = "",
17
+ ...props
18
+ }) => {
19
+ return (
20
+ <div className={`input-wrapper ${className}`}>
21
+ {label && <label className="input-label">{label}</label>}
22
+ <input
23
+ type={type}
24
+ className={`input ${error ? "input-error" : ""}`}
25
+ {...props}
26
+ />
27
+ {error && <span className="error-message">{error}</span>}
28
+ </div>
29
+ );
30
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Loading Component
3
+ * Responsibility: Display loading state
4
+ */
5
+
6
+ /**
7
+ * Loading spinner component
8
+ */
9
+ export const Loading = ({ message = "Loading..." }) => {
10
+ return (
11
+ <div className="loading-container">
12
+ <div className="spinner"></div>
13
+ <p>{message}</p>
14
+ </div>
15
+ );
16
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Modal Component
3
+ * Responsibility: Reusable modal/dialog wrapper
4
+ */
5
+
6
+ "use client";
7
+
8
+ /**
9
+ * Base modal component
10
+ * @param {boolean} isOpen - Modal visibility
11
+ * @param {function} onClose - Close handler
12
+ * @param {string} title - Modal title
13
+ */
14
+ export const Modal = ({ isOpen, onClose, title, children, className = "" }) => {
15
+ if (!isOpen) return null;
16
+
17
+ return (
18
+ <div className="modal-overlay" onClick={onClose}>
19
+ <div
20
+ className={`modal-content ${className}`}
21
+ onClick={(e) => e.stopPropagation()}
22
+ >
23
+ <div className="modal-header">
24
+ <h2>{title}</h2>
25
+ <button onClick={onClose} className="modal-close">
26
+ ×
27
+ </button>
28
+ </div>
29
+ <div className="modal-body">{children}</div>
30
+ </div>
31
+ </div>
32
+ );
33
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * UI Components Export
3
+ * Responsibility: Centralized export for UI primitives
4
+ */
5
+
6
+ export { Button } from "./Button";
7
+ export { Input } from "./Input";
8
+ export { Modal } from "./Modal";
9
+ export { Loading } from "./Loading";
10
+ export { ErrorDisplay } from "./ErrorDisplay";
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Auth Context
3
+ * Responsibility: Manage authentication state and provide auth methods
4
+ */
5
+
6
+ "use client";
7
+
8
+ import { createContext, useContext, useState, useEffect } from "react";
9
+ import { useRouter } from "next/navigation";
10
+ import { authApi } from "@/lib/api/auth.api";
11
+
12
+ const AuthContext = createContext(null);
13
+
14
+ export const useAuth = () => {
15
+ const context = useContext(AuthContext);
16
+ if (!context) {
17
+ throw new Error("useAuth must be used within AuthProvider");
18
+ }
19
+ return context;
20
+ };
21
+
22
+ export const AuthProvider = ({ children }) => {
23
+ const router = useRouter();
24
+ const [user, setUser] = useState(null);
25
+ const [loading, setLoading] = useState(true);
26
+ const [error, setError] = useState(null);
27
+
28
+ /**
29
+ * Initialize auth state from token
30
+ */
31
+ useEffect(() => {
32
+ const initAuth = async () => {
33
+ const token = localStorage.getItem("token");
34
+ if (token) {
35
+ try {
36
+ const response = await authApi.getCurrentUser();
37
+ setUser(response.data);
38
+ } catch (err) {
39
+ localStorage.removeItem("token");
40
+ }
41
+ }
42
+ setLoading(false);
43
+ };
44
+
45
+ initAuth();
46
+ }, []);
47
+
48
+ /**
49
+ * Login user
50
+ */
51
+ const login = async (credentials) => {
52
+ try {
53
+ setError(null);
54
+ const response = await authApi.login(credentials);
55
+ const { token, user: userData } = response.data;
56
+
57
+ localStorage.setItem("token", token);
58
+ setUser(userData);
59
+
60
+ return userData;
61
+ } catch (err) {
62
+ setError(err.message);
63
+ throw err;
64
+ }
65
+ };
66
+
67
+ /**
68
+ * Register user
69
+ */
70
+ const register = async (userData) => {
71
+ try {
72
+ setError(null);
73
+ const response = await authApi.register(userData);
74
+ const { token, user: newUser } = response.data;
75
+
76
+ localStorage.setItem("token", token);
77
+ setUser(newUser);
78
+
79
+ return newUser;
80
+ } catch (err) {
81
+ setError(err.message);
82
+ throw err;
83
+ }
84
+ };
85
+
86
+ /**
87
+ * Logout user
88
+ */
89
+ const logout = async () => {
90
+ try {
91
+ await authApi.logout();
92
+ } catch (err) {
93
+ console.error("Logout error:", err);
94
+ } finally {
95
+ localStorage.removeItem("token");
96
+ setUser(null);
97
+ router.push("/login");
98
+ }
99
+ };
100
+
101
+ const value = {
102
+ user,
103
+ loading,
104
+ error,
105
+ login,
106
+ register,
107
+ logout,
108
+ isAuthenticated: !!user,
109
+ };
110
+
111
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
112
+ };
@@ -0,0 +1,128 @@
1
+ # Template Guide (Docs)
2
+
3
+ This document explains how to use the **Next.js + Redux template** in the `templates/` folder. It focuses only on the template files and how to work with them.
4
+
5
+ ## ✅ What this template includes
6
+
7
+ - Next.js App Router setup (`app/`)
8
+ - Redux Toolkit + RTK Query (`store/`)
9
+ - Auth helpers (`components/auth/` + `hooks/useAuth.js`)
10
+ - Layout shell (`components/layout/`)
11
+ - Reusable UI primitives (`components/ui/`)
12
+ - Docs for API integration, architecture, and examples (`docs/`)
13
+
14
+ ## 🚀 How to use the template
15
+
16
+ ### 1) Copy or scaffold the template
17
+
18
+ - Use this folder as your project base, or copy its contents into a fresh Next.js project.
19
+ - The template already has `package.json`, `next.config.js`, and `jsconfig.json`.
20
+
21
+ ### 2) Install dependencies
22
+
23
+ Use the `package.json` that lives inside `templates/`.
24
+
25
+ ### 3) Configure environment variables
26
+
27
+ 1. Copy `.env.template` into `.env.local`.
28
+ 2. Update the API base URL to your backend:
29
+
30
+ ```
31
+ NEXT_PUBLIC_API_URL=http://localhost:5000/api/v1
32
+ ```
33
+
34
+ ### 4) Start building pages
35
+
36
+ - Public pages go under `app/` (ex: `app/page.jsx`).
37
+ - Protected pages should live inside the `(dashboard)` route group and be wrapped by `withAuth`.
38
+
39
+ ## 🔧 How it works (high level)
40
+
41
+ - **Redux Provider** is mounted in `app/layout.jsx` via `store/ReduxProvider.jsx`.
42
+ - **Auth** flows use RTK Query and `authSlice` to manage tokens.
43
+ - **API calls** are handled by `store/api/apiSlice.js` with automatic token refresh.
44
+ - **Protected routing** uses `components/auth/withAuth.jsx` and `components/auth/withPublic.jsx`.
45
+
46
+ For deeper guides, see:
47
+ - `docs/API_INTEGRATION.md`
48
+ - `docs/ARCHITECTURE.md`
49
+ - `docs/EXAMPLES.md`
50
+ - `docs/PROJECT_STRUCTURE.md`
51
+
52
+ ## 📁 Template folder structure
53
+
54
+ ```
55
+ templates/
56
+ ├── app/
57
+ │ ├── layout.jsx
58
+ │ ├── page.jsx
59
+ │ ├── globals.css
60
+ │ ├── login/
61
+ │ │ └── page.jsx
62
+ │ └── (dashboard)/
63
+ │ ├── layout.jsx
64
+ │ ├── dashboard/
65
+ │ │ └── page.jsx
66
+ │ └── users/
67
+ │ └── page.jsx
68
+ ├── components/
69
+ │ ├── auth/
70
+ │ │ ├── withAuth.jsx
71
+ │ │ └── withPublic.jsx
72
+ │ ├── layout/
73
+ │ │ ├── Header.jsx
74
+ │ │ └── Sidebar.jsx
75
+ │ └── ui/
76
+ │ ├── Button.jsx
77
+ │ ├── ErrorDisplay.jsx
78
+ │ ├── Input.jsx
79
+ │ ├── Loading.jsx
80
+ │ ├── Modal.jsx
81
+ │ └── index.js
82
+ ├── contexts/
83
+ │ └── AuthContext.jsx
84
+ ├── docs/
85
+ │ ├── API_INTEGRATION.md
86
+ │ ├── ARCHITECTURE.md
87
+ │ ├── EXAMPLES.md
88
+ │ ├── PROJECT_STRUCTURE.md
89
+ │ └── README.md
90
+ ├── hooks/
91
+ │ ├── useAsync.js
92
+ │ ├── useAuth.js
93
+ │ └── useForm.js
94
+ ├── lib/
95
+ │ └── api/
96
+ │ ├── apiClient.js
97
+ │ ├── auth.api.js
98
+ │ └── user.api.js
99
+ ├── store/
100
+ │ ├── api/
101
+ │ │ ├── apiSlice.js
102
+ │ │ ├── authApi.js
103
+ │ │ ├── exampleApi.js
104
+ │ │ └── userApi.js
105
+ │ ├── slices/
106
+ │ │ └── authSlice.js
107
+ │ ├── ReduxProvider.jsx
108
+ │ └── store.js
109
+ ├── .env.template
110
+ ├── jsconfig.json
111
+ ├── next.config.js
112
+ └── package.json
113
+ ```
114
+
115
+ ## 🧩 Common customization points
116
+
117
+ - **API base URL** → `store/api/apiSlice.js`
118
+ - **Auth payload shape** → `store/api/authApi.js`
119
+ - **App layout UI** → `components/layout/`
120
+ - **Route protection** → `components/auth/withAuth.jsx`
121
+ - **New RTK Query resources** → copy `store/api/exampleApi.js`
122
+
123
+ ## ✅ Quick sanity checklist
124
+
125
+ - `.env.local` exists and has `NEXT_PUBLIC_API_URL`
126
+ - `app/layout.jsx` wraps the app in `ReduxProvider`
127
+ - Protected pages use `withAuth`
128
+ - You reference APIs via RTK Query hooks (not manual fetch)
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Custom Hook: useAsync
3
+ * Responsibility: Handle async operations with loading and error states
4
+ */
5
+
6
+ "use client";
7
+
8
+ import { useState, useCallback } from "react";
9
+
10
+ export const useAsync = () => {
11
+ const [loading, setLoading] = useState(false);
12
+ const [error, setError] = useState(null);
13
+ const [data, setData] = useState(null);
14
+
15
+ const execute = useCallback(async (asyncFunction) => {
16
+ setLoading(true);
17
+ setError(null);
18
+
19
+ try {
20
+ const result = await asyncFunction();
21
+ setData(result);
22
+ return result;
23
+ } catch (err) {
24
+ setError(err.message);
25
+ throw err;
26
+ } finally {
27
+ setLoading(false);
28
+ }
29
+ }, []);
30
+
31
+ const reset = useCallback(() => {
32
+ setLoading(false);
33
+ setError(null);
34
+ setData(null);
35
+ }, []);
36
+
37
+ return { loading, error, data, execute, reset };
38
+ };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Custom Hook: useAuth
3
+ * Responsibility: Provide authentication state and methods
4
+ *
5
+ * USAGE GUIDE:
6
+ * - Import: import { useAuth } from '@/hooks/useAuth'
7
+ * - In component: const { user, isAuthenticated, login, logout, register } = useAuth()
8
+ *
9
+ * EXAMPLE:
10
+ * ```jsx
11
+ * const { user, isAuthenticated, login, logout, isLoading } = useAuth();
12
+ *
13
+ * const handleLogin = async (credentials) => {
14
+ * try {
15
+ * await login(credentials);
16
+ * // Success - automatically redirects
17
+ * } catch (error) {
18
+ * console.error('Login failed:', error);
19
+ * }
20
+ * };
21
+ * ```
22
+ */
23
+
24
+ "use client";
25
+
26
+ import { useSelector, useDispatch } from "react-redux";
27
+ import { useRouter } from "next/navigation";
28
+ import {
29
+ selectCurrentUser,
30
+ selectIsAuthenticated,
31
+ selectToken,
32
+ logout as logoutAction,
33
+ } from "@/store/slices/authSlice";
34
+ import {
35
+ useLoginMutation,
36
+ useRegisterMutation,
37
+ useLogoutMutation,
38
+ } from "@/store/api/authApi";
39
+
40
+ export const useAuth = () => {
41
+ const dispatch = useDispatch();
42
+ const router = useRouter();
43
+
44
+ const user = useSelector(selectCurrentUser);
45
+ const isAuthenticated = useSelector(selectIsAuthenticated);
46
+ const token = useSelector(selectToken);
47
+
48
+ const [loginMutation, { isLoading: isLoggingIn }] = useLoginMutation();
49
+ const [registerMutation, { isLoading: isRegistering }] =
50
+ useRegisterMutation();
51
+ const [logoutMutation, { isLoading: isLoggingOut }] = useLogoutMutation();
52
+
53
+ const login = async (credentials) => {
54
+ try {
55
+ const result = await loginMutation(credentials).unwrap();
56
+ router.push("/dashboard");
57
+ return result;
58
+ } catch (error) {
59
+ throw error;
60
+ }
61
+ };
62
+
63
+ const register = async (userData) => {
64
+ try {
65
+ const result = await registerMutation(userData).unwrap();
66
+ router.push("/dashboard");
67
+ return result;
68
+ } catch (error) {
69
+ throw error;
70
+ }
71
+ };
72
+
73
+ const logout = async () => {
74
+ try {
75
+ await logoutMutation().unwrap();
76
+ } catch (error) {
77
+ console.error("Logout error:", error);
78
+ } finally {
79
+ dispatch(logoutAction());
80
+ router.push("/login");
81
+ }
82
+ };
83
+
84
+ return {
85
+ user,
86
+ isAuthenticated,
87
+ token,
88
+ login,
89
+ register,
90
+ logout,
91
+ isLoading: isLoggingIn || isRegistering || isLoggingOut,
92
+ };
93
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Custom Hook: useForm
3
+ * Responsibility: Handle form state and validation
4
+ */
5
+
6
+ "use client";
7
+
8
+ import { useState } from "react";
9
+
10
+ export const useForm = (initialValues = {}, validate) => {
11
+ const [values, setValues] = useState(initialValues);
12
+ const [errors, setErrors] = useState({});
13
+ const [touched, setTouched] = useState({});
14
+
15
+ const handleChange = (e) => {
16
+ const { name, value } = e.target;
17
+ setValues((prev) => ({ ...prev, [name]: value }));
18
+
19
+ // Clear error when user starts typing
20
+ if (errors[name]) {
21
+ setErrors((prev) => ({ ...prev, [name]: "" }));
22
+ }
23
+ };
24
+
25
+ const handleBlur = (e) => {
26
+ const { name } = e.target;
27
+ setTouched((prev) => ({ ...prev, [name]: true }));
28
+
29
+ // Validate on blur if validation function provided
30
+ if (validate) {
31
+ const validationErrors = validate(values);
32
+ setErrors(validationErrors);
33
+ }
34
+ };
35
+
36
+ const handleSubmit = (onSubmit) => async (e) => {
37
+ e.preventDefault();
38
+
39
+ // Validate all fields
40
+ if (validate) {
41
+ const validationErrors = validate(values);
42
+ setErrors(validationErrors);
43
+
44
+ if (Object.keys(validationErrors).length > 0) {
45
+ return;
46
+ }
47
+ }
48
+
49
+ await onSubmit(values);
50
+ };
51
+
52
+ const reset = () => {
53
+ setValues(initialValues);
54
+ setErrors({});
55
+ setTouched({});
56
+ };
57
+
58
+ return {
59
+ values,
60
+ errors,
61
+ touched,
62
+ handleChange,
63
+ handleBlur,
64
+ handleSubmit,
65
+ reset,
66
+ };
67
+ };
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26
+ "exclude": ["node_modules"]
27
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * API Client Configuration
3
+ * Responsibility: Centralized HTTP client with interceptors for Next.js
4
+ */
5
+
6
+ const API_BASE_URL =
7
+ process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1";
8
+
9
+ class ApiClient {
10
+ constructor(baseURL = API_BASE_URL) {
11
+ this.baseURL = baseURL;
12
+ }
13
+
14
+ /**
15
+ * Get auth token from storage (client-side only)
16
+ */
17
+ getToken() {
18
+ if (typeof window !== "undefined") {
19
+ return localStorage.getItem("token");
20
+ }
21
+ return null;
22
+ }
23
+
24
+ /**
25
+ * Build headers with auth token
26
+ */
27
+ getHeaders() {
28
+ const headers = {
29
+ "Content-Type": "application/json",
30
+ };
31
+
32
+ const token = this.getToken();
33
+ if (token) {
34
+ headers["Authorization"] = `Bearer ${token}`;
35
+ }
36
+
37
+ return headers;
38
+ }
39
+
40
+ /**
41
+ * Generic request handler
42
+ */
43
+ async request(endpoint, options = {}) {
44
+ const url = `${this.baseURL}${endpoint}`;
45
+ const config = {
46
+ ...options,
47
+ headers: {
48
+ ...this.getHeaders(),
49
+ ...options.headers,
50
+ },
51
+ };
52
+
53
+ try {
54
+ const response = await fetch(url, config);
55
+ const data = await response.json();
56
+
57
+ if (!response.ok) {
58
+ throw new Error(data.message || "Something went wrong");
59
+ }
60
+
61
+ return data;
62
+ } catch (error) {
63
+ console.error("API Error:", error);
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * GET request
70
+ */
71
+ async get(endpoint, params = {}) {
72
+ const queryString = new URLSearchParams(params).toString();
73
+ const url = queryString ? `${endpoint}?${queryString}` : endpoint;
74
+ return this.request(url, { method: "GET" });
75
+ }
76
+
77
+ /**
78
+ * POST request
79
+ */
80
+ async post(endpoint, body) {
81
+ return this.request(endpoint, {
82
+ method: "POST",
83
+ body: JSON.stringify(body),
84
+ });
85
+ }
86
+
87
+ /**
88
+ * PUT request
89
+ */
90
+ async put(endpoint, body) {
91
+ return this.request(endpoint, {
92
+ method: "PUT",
93
+ body: JSON.stringify(body),
94
+ });
95
+ }
96
+
97
+ /**
98
+ * DELETE request
99
+ */
100
+ async delete(endpoint) {
101
+ return this.request(endpoint, { method: "DELETE" });
102
+ }
103
+ }
104
+
105
+ export default new ApiClient();