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,36 @@
1
+ /**
2
+ * Auth API Service
3
+ * Responsibility: Authentication-related API calls
4
+ */
5
+
6
+ import apiClient from "./apiClient";
7
+
8
+ export const authApi = {
9
+ /**
10
+ * Login user
11
+ */
12
+ login: async (credentials) => {
13
+ return apiClient.post("/auth/login", credentials);
14
+ },
15
+
16
+ /**
17
+ * Register user
18
+ */
19
+ register: async (userData) => {
20
+ return apiClient.post("/auth/register", userData);
21
+ },
22
+
23
+ /**
24
+ * Get current user
25
+ */
26
+ getCurrentUser: async () => {
27
+ return apiClient.get("/auth/me");
28
+ },
29
+
30
+ /**
31
+ * Logout user
32
+ */
33
+ logout: async () => {
34
+ return apiClient.post("/auth/logout");
35
+ },
36
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * User API Service
3
+ * Responsibility: User-related API calls
4
+ */
5
+
6
+ import apiClient from "./apiClient";
7
+
8
+ export const userApi = {
9
+ /**
10
+ * Get all users with pagination
11
+ */
12
+ getUsers: async (params = {}) => {
13
+ return apiClient.get("/users", params);
14
+ },
15
+
16
+ /**
17
+ * Get user by ID
18
+ */
19
+ getUserById: async (id) => {
20
+ return apiClient.get(`/users/${id}`);
21
+ },
22
+
23
+ /**
24
+ * Create user
25
+ */
26
+ createUser: async (userData) => {
27
+ return apiClient.post("/users", userData);
28
+ },
29
+
30
+ /**
31
+ * Update user
32
+ */
33
+ updateUser: async (id, userData) => {
34
+ return apiClient.put(`/users/${id}`, userData);
35
+ },
36
+
37
+ /**
38
+ * Delete user
39
+ */
40
+ deleteUser: async (id) => {
41
+ return apiClient.delete(`/users/${id}`);
42
+ },
43
+ };
@@ -0,0 +1,18 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ // API proxy configuration (optional - only if you want to proxy API calls)
5
+ async rewrites() {
6
+ const apiUrl =
7
+ process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1";
8
+
9
+ return [
10
+ {
11
+ source: "/api/:path*",
12
+ destination: `${apiUrl}/:path*`,
13
+ },
14
+ ];
15
+ },
16
+ };
17
+
18
+ module.exports = nextConfig;
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "nextjs-template",
3
+ "version": "2.0.0",
4
+ "description": "Production-ready Next.js App Router frontend template with Redux & RTK Query",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "next dev",
8
+ "build": "next build",
9
+ "start": "next start",
10
+ "lint": "next lint"
11
+ },
12
+ "dependencies": {
13
+ "next": "^14.0.0",
14
+ "react": "^18.2.0",
15
+ "react-dom": "^18.2.0",
16
+ "@reduxjs/toolkit": "^2.0.1",
17
+ "react-redux": "^9.0.4"
18
+ },
19
+ "devDependencies": {
20
+ "eslint": "^8.54.0",
21
+ "eslint-config-next": "^14.0.0"
22
+ }
23
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Redux Store Provider
3
+ * Responsibility: Wrap the app with Redux store
4
+ *
5
+ * USAGE GUIDE:
6
+ * - Import this in your root layout (app/layout.jsx)
7
+ * - Wrap all components that need access to Redux store
8
+ *
9
+ * EXAMPLE:
10
+ * ```jsx
11
+ * import { ReduxProvider } from '@/store/ReduxProvider';
12
+ *
13
+ * export default function RootLayout({ children }) {
14
+ * return (
15
+ * <html lang="en">
16
+ * <body>
17
+ * <ReduxProvider>
18
+ * {children}
19
+ * </ReduxProvider>
20
+ * </body>
21
+ * </html>
22
+ * );
23
+ * }
24
+ * ```
25
+ */
26
+
27
+ "use client";
28
+
29
+ import { Provider } from "react-redux";
30
+ import { store } from "./store";
31
+
32
+ export function ReduxProvider({ children }) {
33
+ return <Provider store={store}>{children}</Provider>;
34
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * RTK Query API Slice with Auto Token Refresh
3
+ * Responsibility: Base API configuration with automatic token refresh on 401
4
+ *
5
+ * USAGE GUIDE:
6
+ * - This is the base API slice that handles all API calls
7
+ * - Automatically adds Authorization header with access token
8
+ * - Automatically refreshes token when it expires (401 error)
9
+ * - All other API services extend from this base
10
+ *
11
+ * HOW IT WORKS:
12
+ * 1. Request sent with access token
13
+ * 2. If 401 error received, automatically tries to refresh token
14
+ * 3. If refresh successful, retries original request
15
+ * 4. If refresh fails, logs out user
16
+ */
17
+
18
+ import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
19
+ import { updateAccessToken, logout } from "../slices/authSlice";
20
+
21
+ const API_BASE_URL =
22
+ process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api/v1";
23
+
24
+ // Base query with auth token
25
+ const baseQuery = fetchBaseQuery({
26
+ baseUrl: API_BASE_URL,
27
+ prepareHeaders: (headers, { getState }) => {
28
+ const token = getState().auth.accessToken;
29
+
30
+ if (token) {
31
+ headers.set("authorization", `Bearer ${token}`);
32
+ }
33
+
34
+ return headers;
35
+ },
36
+ });
37
+
38
+ // Base query with re-authentication (token refresh)
39
+ const baseQueryWithReauth = async (args, api, extraOptions) => {
40
+ let result = await baseQuery(args, api, extraOptions);
41
+
42
+ // If we get a 401 error, try to refresh the token
43
+ if (result?.error?.status === 401) {
44
+ console.log("Token expired, attempting to refresh...");
45
+
46
+ const refreshToken = api.getState().auth.refreshToken;
47
+
48
+ if (refreshToken) {
49
+ // Try to get a new access token
50
+ const refreshResult = await baseQuery(
51
+ {
52
+ url: "/auth/refresh",
53
+ method: "POST",
54
+ body: { refreshToken },
55
+ },
56
+ api,
57
+ extraOptions
58
+ );
59
+
60
+ if (refreshResult?.data) {
61
+ // Store the new access token
62
+ const { accessToken } = refreshResult.data;
63
+ api.dispatch(updateAccessToken(accessToken));
64
+
65
+ console.log(
66
+ "Token refreshed successfully, retrying original request..."
67
+ );
68
+
69
+ // Retry the original request with new token
70
+ result = await baseQuery(args, api, extraOptions);
71
+ } else {
72
+ // Refresh token failed - logout user
73
+ console.log("Token refresh failed, logging out...");
74
+ api.dispatch(logout());
75
+
76
+ // Redirect to login page
77
+ if (typeof window !== "undefined") {
78
+ window.location.href = "/login";
79
+ }
80
+ }
81
+ } else {
82
+ // No refresh token available - logout user
83
+ api.dispatch(logout());
84
+
85
+ // Redirect to login page
86
+ if (typeof window !== "undefined") {
87
+ window.location.href = "/login";
88
+ }
89
+ }
90
+ }
91
+
92
+ return result;
93
+ };
94
+
95
+ // Create the API slice
96
+ export const apiSlice = createApi({
97
+ reducerPath: "api",
98
+ baseQuery: baseQueryWithReauth,
99
+ tagTypes: ["User"
100
+ ✓ Starting...
101
+ `destination` does not start with `/`, `http://`, or `https://` for route {"source":"/api/:path*","destination":"undefined/:path*"}
102
+
103
+
104
+ Error: Invalid rewrite found
105
+
106
+ coder-bird@root-bird:~/Desktop/next-template$ , "Auth"], // Add more tag types as needed
107
+ endpoints: (builder) => ({}), // Endpoints will be injected from other files
108
+ });
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Auth API Service using RTK Query
3
+ * Responsibility: Handle all authentication-related API calls
4
+ *
5
+ * USAGE GUIDE:
6
+ * - Import: import { useLoginMutation, useRegisterMutation, useLogoutMutation, useGetCurrentUserQuery } from '@/store/api/authApi'
7
+ * - In component: const [login, { isLoading, error }] = useLoginMutation()
8
+ * - Call mutation: await login({ email, password }).unwrap()
9
+ *
10
+ * EXAMPLE:
11
+ * ```jsx
12
+ * const [login, { isLoading }] = useLoginMutation();
13
+ *
14
+ * const handleLogin = async (credentials) => {
15
+ * try {
16
+ * const result = await login(credentials).unwrap();
17
+ * // Success - user is automatically logged in
18
+ * router.push('/dashboard');
19
+ * } catch (error) {
20
+ * // Handle error
21
+ * console.error('Login failed:', error);
22
+ * }
23
+ * };
24
+ * ```
25
+ */
26
+
27
+ import { apiSlice } from "./apiSlice";
28
+ import { setCredentials, logout as logoutAction } from "../slices/authSlice";
29
+
30
+ export const authApi = apiSlice.injectEndpoints({
31
+ endpoints: (builder) => ({
32
+ // Login
33
+ login: builder.mutation({
34
+ query: (credentials) => ({
35
+ url: "/auth/login",
36
+ method: "POST",
37
+ body: credentials,
38
+ }),
39
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
40
+ try {
41
+ const { data } = await queryFulfilled;
42
+ // Store user data and tokens in Redux store
43
+ dispatch(
44
+ setCredentials({
45
+ user: data.user,
46
+ accessToken: data.accessToken,
47
+ refreshToken: data.refreshToken,
48
+ })
49
+ );
50
+ } catch (error) {
51
+ console.error("Login error:", error);
52
+ }
53
+ },
54
+ invalidatesTags: ["Auth"],
55
+ }),
56
+
57
+ // Register
58
+ register: builder.mutation({
59
+ query: (userData) => ({
60
+ url: "/auth/register",
61
+ method: "POST",
62
+ body: userData,
63
+ }),
64
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
65
+ try {
66
+ const { data } = await queryFulfilled;
67
+ // Store user data and tokens in Redux store
68
+ dispatch(
69
+ setCredentials({
70
+ user: data.user,
71
+ accessToken: data.accessToken,
72
+ refreshToken: data.refreshToken,
73
+ })
74
+ );
75
+ } catch (error) {
76
+ console.error("Register error:", error);
77
+ }
78
+ },
79
+ invalidatesTags: ["Auth"],
80
+ }),
81
+
82
+ // Logout
83
+ logout: builder.mutation({
84
+ query: () => ({
85
+ url: "/auth/logout",
86
+ method: "POST",
87
+ }),
88
+ async onQueryStarted(arg, { dispatch, queryFulfilled }) {
89
+ try {
90
+ await queryFulfilled;
91
+ } catch (error) {
92
+ console.error("Logout error:", error);
93
+ } finally {
94
+ // Clear auth state regardless of API success
95
+ dispatch(logoutAction());
96
+ }
97
+ },
98
+ invalidatesTags: ["Auth", "User"],
99
+ }),
100
+
101
+ // Get current user
102
+ getCurrentUser: builder.query({
103
+ query: () => "/auth/me",
104
+ providesTags: ["User"],
105
+ }),
106
+
107
+ // Refresh token (usually called automatically by baseQueryWithReauth)
108
+ refreshToken: builder.mutation({
109
+ query: (refreshToken) => ({
110
+ url: "/auth/refresh",
111
+ method: "POST",
112
+ body: { refreshToken },
113
+ }),
114
+ }),
115
+
116
+ // Forgot password
117
+ forgotPassword: builder.mutation({
118
+ query: (email) => ({
119
+ url: "/auth/forgot-password",
120
+ method: "POST",
121
+ body: { email },
122
+ }),
123
+ }),
124
+
125
+ // Reset password
126
+ resetPassword: builder.mutation({
127
+ query: ({ token, password }) => ({
128
+ url: "/auth/reset-password",
129
+ method: "POST",
130
+ body: { token, password },
131
+ }),
132
+ }),
133
+
134
+ // Change password
135
+ changePassword: builder.mutation({
136
+ query: ({ oldPassword, newPassword }) => ({
137
+ url: "/auth/change-password",
138
+ method: "POST",
139
+ body: { oldPassword, newPassword },
140
+ }),
141
+ }),
142
+ }),
143
+ });
144
+
145
+ // Export hooks for usage in components
146
+ export const {
147
+ useLoginMutation,
148
+ useRegisterMutation,
149
+ useLogoutMutation,
150
+ useGetCurrentUserQuery,
151
+ useRefreshTokenMutation,
152
+ useForgotPasswordMutation,
153
+ useResetPasswordMutation,
154
+ useChangePasswordMutation,
155
+ } = authApi;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Example API Service Template
3
+ * Responsibility: Template for creating new API services
4
+ *
5
+ * USAGE GUIDE:
6
+ * Copy this file and rename it to match your resource (e.g., postsApi.js, productsApi.js)
7
+ * Replace 'Example' with your resource name
8
+ *
9
+ * STEPS TO CREATE NEW API SERVICE:
10
+ * 1. Copy this file
11
+ * 2. Rename file and variables
12
+ * 3. Define your tag types in store/api/apiSlice.js
13
+ * 4. Update endpoints with your API routes
14
+ * 5. Import and use hooks in your components
15
+ *
16
+ * EXAMPLE FOR POSTS API:
17
+ * ```jsx
18
+ * // store/api/postsApi.js
19
+ * import { apiSlice } from './apiSlice';
20
+ *
21
+ * export const postsApi = apiSlice.injectEndpoints({
22
+ * endpoints: (builder) => ({
23
+ * getPosts: builder.query({
24
+ * query: () => '/posts',
25
+ * providesTags: ['Posts'],
26
+ * }),
27
+ * createPost: builder.mutation({
28
+ * query: (postData) => ({
29
+ * url: '/posts',
30
+ * method: 'POST',
31
+ * body: postData,
32
+ * }),
33
+ * invalidatesTags: ['Posts'],
34
+ * }),
35
+ * }),
36
+ * });
37
+ *
38
+ * export const { useGetPostsQuery, useCreatePostMutation } = postsApi;
39
+ * ```
40
+ */
41
+
42
+ import { apiSlice } from "./apiSlice";
43
+
44
+ export const exampleApi = apiSlice.injectEndpoints({
45
+ endpoints: (builder) => ({
46
+ // GET - Fetch list of items
47
+ getExamples: builder.query({
48
+ query: (params = {}) => {
49
+ const queryString = new URLSearchParams(params).toString();
50
+ return `/examples${queryString ? `?${queryString}` : ""}`;
51
+ },
52
+ providesTags: (result) =>
53
+ result
54
+ ? [
55
+ ...result.data.map(({ id }) => ({ type: "Example", id })),
56
+ { type: "Example", id: "LIST" },
57
+ ]
58
+ : [{ type: "Example", id: "LIST" }],
59
+ }),
60
+
61
+ // GET - Fetch single item by ID
62
+ getExampleById: builder.query({
63
+ query: (id) => `/examples/${id}`,
64
+ providesTags: (result, error, id) => [{ type: "Example", id }],
65
+ }),
66
+
67
+ // POST - Create new item
68
+ createExample: builder.mutation({
69
+ query: (data) => ({
70
+ url: "/examples",
71
+ method: "POST",
72
+ body: data,
73
+ }),
74
+ invalidatesTags: [{ type: "Example", id: "LIST" }],
75
+ }),
76
+
77
+ // PUT - Update item
78
+ updateExample: builder.mutation({
79
+ query: ({ id, ...data }) => ({
80
+ url: `/examples/${id}`,
81
+ method: "PUT",
82
+ body: data,
83
+ }),
84
+ invalidatesTags: (result, error, { id }) => [
85
+ { type: "Example", id },
86
+ { type: "Example", id: "LIST" },
87
+ ],
88
+ }),
89
+
90
+ // DELETE - Delete item
91
+ deleteExample: builder.mutation({
92
+ query: (id) => ({
93
+ url: `/examples/${id}`,
94
+ method: "DELETE",
95
+ }),
96
+ invalidatesTags: [{ type: "Example", id: "LIST" }],
97
+ }),
98
+ }),
99
+ });
100
+
101
+ // Export hooks for usage in components
102
+ export const {
103
+ useGetExamplesQuery,
104
+ useGetExampleByIdQuery,
105
+ useCreateExampleMutation,
106
+ useUpdateExampleMutation,
107
+ useDeleteExampleMutation,
108
+ } = exampleApi;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * User API Service using RTK Query
3
+ * Responsibility: Handle all user-related API calls
4
+ *
5
+ * USAGE GUIDE:
6
+ * - Import: import { useGetUsersQuery, useGetUserByIdQuery, useUpdateUserMutation } from '@/store/api/userApi'
7
+ * - In component: const { data: users, isLoading, error } = useGetUsersQuery()
8
+ * - With params: const { data: user } = useGetUserByIdQuery(userId)
9
+ *
10
+ * EXAMPLE:
11
+ * ```jsx
12
+ * const { data: users, isLoading, error, refetch } = useGetUsersQuery({ page: 1, limit: 10 });
13
+ *
14
+ * const [updateUser, { isLoading: isUpdating }] = useUpdateUserMutation();
15
+ *
16
+ * const handleUpdate = async (userId, userData) => {
17
+ * try {
18
+ * await updateUser({ id: userId, ...userData }).unwrap();
19
+ * // Success
20
+ * } catch (error) {
21
+ * // Handle error
22
+ * }
23
+ * };
24
+ * ```
25
+ */
26
+
27
+ import { apiSlice } from "./apiSlice";
28
+
29
+ export const userApi = apiSlice.injectEndpoints({
30
+ endpoints: (builder) => ({
31
+ // Get all users (with pagination and filters)
32
+ getUsers: builder.query({
33
+ query: (params = {}) => {
34
+ const queryString = new URLSearchParams(params).toString();
35
+ return `/users${queryString ? `?${queryString}` : ""}`;
36
+ },
37
+ providesTags: (result) =>
38
+ result
39
+ ? [
40
+ ...result.users.map(({ id }) => ({ type: "User", id })),
41
+ { type: "User", id: "LIST" },
42
+ ]
43
+ : [{ type: "User", id: "LIST" }],
44
+ }),
45
+
46
+ // Get single user by ID
47
+ getUserById: builder.query({
48
+ query: (id) => `/users/${id}`,
49
+ providesTags: (result, error, id) => [{ type: "User", id }],
50
+ }),
51
+
52
+ // Create new user
53
+ createUser: builder.mutation({
54
+ query: (userData) => ({
55
+ url: "/users",
56
+ method: "POST",
57
+ body: userData,
58
+ }),
59
+ invalidatesTags: [{ type: "User", id: "LIST" }],
60
+ }),
61
+
62
+ // Update user
63
+ updateUser: builder.mutation({
64
+ query: ({ id, ...userData }) => ({
65
+ url: `/users/${id}`,
66
+ method: "PUT",
67
+ body: userData,
68
+ }),
69
+ invalidatesTags: (result, error, { id }) => [{ type: "User", id }],
70
+ }),
71
+
72
+ // Delete user
73
+ deleteUser: builder.mutation({
74
+ query: (id) => ({
75
+ url: `/users/${id}`,
76
+ method: "DELETE",
77
+ }),
78
+ invalidatesTags: [{ type: "User", id: "LIST" }],
79
+ }),
80
+
81
+ // Update user profile
82
+ updateProfile: builder.mutation({
83
+ query: (userData) => ({
84
+ url: "/users/profile",
85
+ method: "PUT",
86
+ body: userData,
87
+ }),
88
+ invalidatesTags: ["User"],
89
+ }),
90
+
91
+ // Upload user avatar
92
+ uploadAvatar: builder.mutation({
93
+ query: (formData) => ({
94
+ url: "/users/avatar",
95
+ method: "POST",
96
+ body: formData,
97
+ // Don't set Content-Type header, let browser set it for FormData
98
+ headers: {},
99
+ }),
100
+ invalidatesTags: ["User"],
101
+ }),
102
+ }),
103
+ });
104
+
105
+ // Export hooks for usage in components
106
+ export const {
107
+ useGetUsersQuery,
108
+ useGetUserByIdQuery,
109
+ useCreateUserMutation,
110
+ useUpdateUserMutation,
111
+ useDeleteUserMutation,
112
+ useUpdateProfileMutation,
113
+ useUploadAvatarMutation,
114
+ } = userApi;