ntc-dev-kit 1.0.0__py3-none-any.whl

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 (148) hide show
  1. ntc/__init__.py +5 -0
  2. ntc/app_creator.py +296 -0
  3. ntc/cli.py +4 -0
  4. ntc/cli_enhanced.py +820 -0
  5. ntc/composer/__init__.py +0 -0
  6. ntc/composer/main.py +443 -0
  7. ntc/deployment/__init__.py +0 -0
  8. ntc/templates/__init__.py +2 -0
  9. ntc/templates/auth_app/__init__.py +0 -0
  10. ntc/templates/auth_app/admin.py +24 -0
  11. ntc/templates/auth_app/apps.py +6 -0
  12. ntc/templates/auth_app/models.py +30 -0
  13. ntc/templates/auth_app/serializers.py +101 -0
  14. ntc/templates/auth_app/urls.py +19 -0
  15. ntc/templates/auth_app/views.py +175 -0
  16. ntc/templates/drf_structure/__init__.py +1 -0
  17. ntc/templates/drf_structure/drf_structure.md +83 -0
  18. ntc/templates/drf_structure/main.py +389 -0
  19. ntc/templates/drf_structure/structure.json +126 -0
  20. ntc/templates/drf_structure/templates/.entry/start_core_backend.sh +37 -0
  21. ntc/templates/drf_structure/templates/.entry/start_tasks_backend.sh +30 -0
  22. ntc/templates/drf_structure/templates/CLAUDE.md +194 -0
  23. ntc/templates/drf_structure/templates/README.md +117 -0
  24. ntc/templates/drf_structure/templates/apps/app_admin.py +3 -0
  25. ntc/templates/drf_structure/templates/apps/app_apps.py +6 -0
  26. ntc/templates/drf_structure/templates/apps/app_models.py +4 -0
  27. ntc/templates/drf_structure/templates/apps/app_urls.py +8 -0
  28. ntc/templates/drf_structure/templates/apps/app_views.py +6 -0
  29. ntc/templates/drf_structure/templates/apps/common_apps.py +6 -0
  30. ntc/templates/drf_structure/templates/apps/common_models.py +8 -0
  31. ntc/templates/drf_structure/templates/config/gunicorn_config.py +9 -0
  32. ntc/templates/drf_structure/templates/config/http.conf +33 -0
  33. ntc/templates/drf_structure/templates/core/base_model.py +9 -0
  34. ntc/templates/drf_structure/templates/core/formatter.py +5 -0
  35. ntc/templates/drf_structure/templates/core/logger.py +8 -0
  36. ntc/templates/drf_structure/templates/core/pagination.py +6 -0
  37. ntc/templates/drf_structure/templates/core/response_wrapper.py +7 -0
  38. ntc/templates/drf_structure/templates/core/services/azure/email/backend.py +108 -0
  39. ntc/templates/drf_structure/templates/core/services/azure/email/client.py +296 -0
  40. ntc/templates/drf_structure/templates/core/services/azure/email/notifier.py +102 -0
  41. ntc/templates/drf_structure/templates/core/services/object_storage.py +285 -0
  42. ntc/templates/drf_structure/templates/django_project/asgi.py +5 -0
  43. ntc/templates/drf_structure/templates/django_project/celery.py +8 -0
  44. ntc/templates/drf_structure/templates/django_project/dev_settings.py +2 -0
  45. ntc/templates/drf_structure/templates/django_project/django_settings.txt +322 -0
  46. ntc/templates/drf_structure/templates/django_project/management_command.py +114 -0
  47. ntc/templates/drf_structure/templates/django_project/prod_settings.py +3 -0
  48. ntc/templates/drf_structure/templates/django_project/schema.py +7 -0
  49. ntc/templates/drf_structure/templates/django_project/urls.py +6 -0
  50. ntc/templates/drf_structure/templates/django_project/wsgi.py +5 -0
  51. ntc/templates/drf_structure/templates/docker/docker-compose.yml +11 -0
  52. ntc/templates/drf_structure/templates/knowledge/knowledge_description.md +201 -0
  53. ntc/templates/drf_structure/templates/knowledge/knowledge_design_pattern.md +722 -0
  54. ntc/templates/drf_structure/templates/knowledge/knowledge_structure.md +151 -0
  55. ntc/templates/drf_structure/templates/root/README.md +307 -0
  56. ntc/templates/drf_structure/templates/root/django_env.txt +98 -0
  57. ntc/templates/drf_structure/templates/root/django_requirements.txt +24 -0
  58. ntc/templates/drf_structure/templates/root/gitignore.txt +6 -0
  59. ntc/templates/drf_structure/templates/root/manage.py +11 -0
  60. ntc/templates/flutter/.gitignore +44 -0
  61. ntc/templates/flutter/.knowledge/knowledge_description.md +215 -0
  62. ntc/templates/flutter/.knowledge/knowledge_design_pattern.md +310 -0
  63. ntc/templates/flutter/.knowledge/knowledge_structure.md +83 -0
  64. ntc/templates/flutter/CLAUDE.md +190 -0
  65. ntc/templates/flutter/__init__.py +0 -0
  66. ntc/templates/flutter/analysis_options.yaml +9 -0
  67. ntc/templates/flutter/main.py +187 -0
  68. ntc/templates/flutter/pubspec.yaml +65 -0
  69. ntc/templates/flutter/structure.json +51 -0
  70. ntc/templates/nextjs/.env.example +10 -0
  71. ntc/templates/nextjs/.gitignore +36 -0
  72. ntc/templates/nextjs/CLAUDE.md +181 -0
  73. ntc/templates/nextjs/README.md +94 -0
  74. ntc/templates/nextjs/__init__.py +0 -0
  75. ntc/templates/nextjs/knowledge/knowledge_description.md +208 -0
  76. ntc/templates/nextjs/knowledge/knowledge_design_pattern.md +460 -0
  77. ntc/templates/nextjs/knowledge/knowledge_structure.md +75 -0
  78. ntc/templates/nextjs/main.py +224 -0
  79. ntc/templates/nextjs/package.json +25 -0
  80. ntc/templates/nextjs/src/app/globals.css +26 -0
  81. ntc/templates/nextjs/src/app/layout.tsx +59 -0
  82. ntc/templates/nextjs/src/app/page.tsx +21 -0
  83. ntc/templates/nextjs/src/components/Header.tsx +16 -0
  84. ntc/templates/nextjs/src/services/api.ts +81 -0
  85. ntc/templates/nextjs/src/utils/constants.ts +1 -0
  86. ntc/templates/nextjs/structure.json +32 -0
  87. ntc/templates/nextjs/tsconfig.json +27 -0
  88. ntc/templates/react_admin/.env.example +1 -0
  89. ntc/templates/react_admin/.gitignore +10 -0
  90. ntc/templates/react_admin/CLAUDE.md +210 -0
  91. ntc/templates/react_admin/README.md +72 -0
  92. ntc/templates/react_admin/__init__.py +0 -0
  93. ntc/templates/react_admin/index.html +13 -0
  94. ntc/templates/react_admin/knowledge/knowledge_description.md +202 -0
  95. ntc/templates/react_admin/knowledge/knowledge_design_pattern.md +448 -0
  96. ntc/templates/react_admin/knowledge/knowledge_structure.md +97 -0
  97. ntc/templates/react_admin/main.py +208 -0
  98. ntc/templates/react_admin/package.json +35 -0
  99. ntc/templates/react_admin/src/App.tsx +60 -0
  100. ntc/templates/react_admin/src/components/ProtectedRoute.tsx +16 -0
  101. ntc/templates/react_admin/src/components/Sidebar.tsx +90 -0
  102. ntc/templates/react_admin/src/components/TopNav.tsx +44 -0
  103. ntc/templates/react_admin/src/context/AuthContext.tsx +107 -0
  104. ntc/templates/react_admin/src/context/ThemeContext.tsx +34 -0
  105. ntc/templates/react_admin/src/context/WorkspaceContext.tsx +28 -0
  106. ntc/templates/react_admin/src/hooks/useApi.ts +39 -0
  107. ntc/templates/react_admin/src/index.css +34 -0
  108. ntc/templates/react_admin/src/layouts/AuthLayout.tsx +23 -0
  109. ntc/templates/react_admin/src/layouts/DashboardLayout.tsx +24 -0
  110. ntc/templates/react_admin/src/main.tsx +5 -0
  111. ntc/templates/react_admin/src/pages/DashboardPage.tsx +10 -0
  112. ntc/templates/react_admin/src/pages/auth/LoginPage.tsx +79 -0
  113. ntc/templates/react_admin/src/pages/auth/RegisterPage.tsx +93 -0
  114. ntc/templates/react_admin/src/services/api.service.ts +44 -0
  115. ntc/templates/react_admin/src/services/base.service.ts +193 -0
  116. ntc/templates/react_admin/structure.json +52 -0
  117. ntc/templates/react_admin/tsconfig.app.json +25 -0
  118. ntc/templates/react_admin/tsconfig.json +7 -0
  119. ntc/templates/react_admin/tsconfig.node.json +19 -0
  120. ntc/templates/react_admin/vite.config.ts +13 -0
  121. ntc/templates/react_structure/CLAUDE.md +176 -0
  122. ntc/templates/react_structure/__init__.py +1 -0
  123. ntc/templates/react_structure/index.css +21 -0
  124. ntc/templates/react_structure/index.html +13 -0
  125. ntc/templates/react_structure/knowledge/knowledge_description.md +170 -0
  126. ntc/templates/react_structure/knowledge/knowledge_design_pattern.md +411 -0
  127. ntc/templates/react_structure/knowledge/knowledge_structure.md +74 -0
  128. ntc/templates/react_structure/main.py +217 -0
  129. ntc/templates/react_structure/package.json +32 -0
  130. ntc/templates/react_structure/src/App.tsx +63 -0
  131. ntc/templates/react_structure/src/components/Header.tsx +51 -0
  132. ntc/templates/react_structure/src/main.tsx +5 -0
  133. ntc/templates/react_structure/src/services/api.ts +90 -0
  134. ntc/templates/react_structure/src/utils/constants.ts +1 -0
  135. ntc/templates/react_structure/structure.json +65 -0
  136. ntc/templates/react_structure/tsconfig.app.json +25 -0
  137. ntc/templates/react_structure/tsconfig.json +7 -0
  138. ntc/templates/react_structure/tsconfig.node.json +19 -0
  139. ntc/templates/react_structure/vite.config.ts +12 -0
  140. ntc/utils/__init__.py +1 -0
  141. ntc/utils/environment.py +67 -0
  142. ntc/utils/output.py +41 -0
  143. ntc_dev_kit-1.0.0.dist-info/METADATA +188 -0
  144. ntc_dev_kit-1.0.0.dist-info/RECORD +148 -0
  145. ntc_dev_kit-1.0.0.dist-info/WHEEL +5 -0
  146. ntc_dev_kit-1.0.0.dist-info/entry_points.txt +2 -0
  147. ntc_dev_kit-1.0.0.dist-info/licenses/LICENSE +21 -0
  148. ntc_dev_kit-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,208 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+ import json
5
+ import shutil
6
+ from colorama import Fore, Style
7
+ from ntc.utils.output import print_success, print_error, print_warning, print_info
8
+
9
+ def replace_variables(content, variables):
10
+ """Replace placeholders with actual variables."""
11
+ for key, value in variables.items():
12
+ content = content.replace(f"{{{key}}}", value)
13
+ return content
14
+
15
+ def get_template_content(template_name, variables):
16
+ """Load template content and replace variables."""
17
+ if template_name.startswith("react_admin/"):
18
+ template_name = template_name[len("react_admin/"):]
19
+
20
+ template_path = os.path.join(os.path.dirname(__file__), template_name)
21
+ if not os.path.exists(template_path):
22
+ return ""
23
+ with open(template_path, "r", encoding="utf-8") as f:
24
+ content = f.read()
25
+ return replace_variables(content, variables)
26
+
27
+ def create_from_json(base_path, structure, variables):
28
+ """Recursively create directories and files from JSON."""
29
+ for name, content in structure.items():
30
+ name = replace_variables(name, variables)
31
+ full_path = os.path.join(base_path, name)
32
+
33
+ if isinstance(content, dict):
34
+ if "template" in content:
35
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
36
+ with open(full_path, "w", encoding="utf-8") as f:
37
+ f.write(get_template_content(content["template"], variables))
38
+ else:
39
+ os.makedirs(full_path, exist_ok=True)
40
+ create_from_json(full_path, content, variables)
41
+ else:
42
+ os.makedirs(os.path.dirname(full_path), exist_ok=True)
43
+ with open(full_path, "w", encoding="utf-8") as f:
44
+ f.write(replace_variables(content or "", variables))
45
+
46
+ def install_npm_dependencies(project_root):
47
+ """Install npm dependencies for React admin project."""
48
+ print_info("Installing npm dependencies...")
49
+
50
+ if not shutil.which("npm"):
51
+ print_error("npm not found. Please install Node.js and npm first.")
52
+ print_info("Visit https://nodejs.org/ to download Node.js")
53
+ return False
54
+
55
+ try:
56
+ result = subprocess.run(
57
+ ["npm", "install"],
58
+ cwd=project_root,
59
+ check=True,
60
+ capture_output=True,
61
+ text=True
62
+ )
63
+ print_success("npm dependencies installed successfully!")
64
+ return True
65
+ except subprocess.CalledProcessError as e:
66
+ print_error(f"Failed to install npm dependencies: {e}")
67
+ print_info("You can manually run 'npm install' in the project directory later.")
68
+ return False
69
+
70
+ def init_react_admin_project(base_path, project_name):
71
+ """Initialize React admin dashboard project structure and setup environment."""
72
+ structure_file = os.path.join(os.path.dirname(__file__), "structure.json")
73
+
74
+ if not os.path.exists(structure_file):
75
+ print_error(f"Structure file not found: {structure_file}")
76
+ return
77
+
78
+ with open(structure_file, "r", encoding="utf-8") as f:
79
+ structure = json.load(f)
80
+
81
+ project_root = os.path.join(base_path, project_name)
82
+ os.makedirs(project_root, exist_ok=True)
83
+
84
+ variables = {
85
+ "project_name": project_name,
86
+ }
87
+
88
+ create_from_json(project_root, structure["{project_name}"], variables)
89
+ print_success(f"React admin dashboard '{project_name}' initialized at: {project_root}")
90
+
91
+ # Ask user for dependency installation
92
+ print(f"\n{Fore.CYAN}{Style.BRIGHT}📦 NODE.JS DEPENDENCY SETUP{Style.RESET_ALL}")
93
+ print("─" * 40)
94
+ print(f"{Fore.YELLOW}{Style.BRIGHT}[1]{Style.RESET_ALL} {Fore.WHITE}Install dependencies now (npm install){Style.RESET_ALL}")
95
+ print(f"{Fore.YELLOW}{Style.BRIGHT}[2]{Style.RESET_ALL} {Fore.WHITE}Use existing node_modules (if available){Style.RESET_ALL}")
96
+ print(f"{Fore.YELLOW}{Style.BRIGHT}[3]{Style.RESET_ALL} {Fore.WHITE}Skip dependency installation{Style.RESET_ALL}")
97
+
98
+ while True:
99
+ deps_choice = input(f"{Fore.WHITE}{Style.BRIGHT}❓ Select dependency option (1-3): {Style.RESET_ALL}").strip()
100
+
101
+ # Clear screen after selection
102
+ os.system('cls' if os.name == 'nt' else 'clear')
103
+
104
+ if deps_choice == "1":
105
+ install_npm_dependencies(project_root)
106
+ break
107
+ elif deps_choice == "2":
108
+ node_modules_path = os.path.join(project_root, "node_modules")
109
+ if os.path.exists(node_modules_path):
110
+ print_success("Found existing node_modules directory!")
111
+ print_info("Using existing dependencies. Run 'npm install' if you need to update them.")
112
+ else:
113
+ print_warning("No existing node_modules found in this location.")
114
+ print_info("You may need to run 'npm install' manually later.")
115
+ break
116
+ elif deps_choice == "3":
117
+ print_info("Skipping dependency installation.")
118
+ print_info("Remember to run 'npm install' before starting development.")
119
+ break
120
+ else:
121
+ print_error("Invalid choice. Please select 1, 2, or 3.")
122
+
123
+ # Show next steps
124
+ print(f"\n{Fore.GREEN}{Style.BRIGHT}🎉 PROJECT READY!{Style.RESET_ALL}")
125
+ print("─" * 30)
126
+ print_info("Next steps:")
127
+ print(f" 1. cd {project_name}")
128
+ if deps_choice != "1":
129
+ print(" 2. npm install")
130
+ print(" 3. npm run dev")
131
+ else:
132
+ print(" 2. npm run dev")
133
+ print_info("Your React admin dashboard will be available at http://localhost:5173")
134
+ print_info("Make sure your backend API is running on http://localhost:8000")
135
+
136
+ def init_react_admin_project_in_current_dir(project_name):
137
+ """Initialize React admin dashboard project structure in the current directory."""
138
+ structure_file = os.path.join(os.path.dirname(__file__), "structure.json")
139
+
140
+ if not os.path.exists(structure_file):
141
+ print_error(f"Structure file not found: {structure_file}")
142
+ return
143
+
144
+ with open(structure_file, "r", encoding="utf-8") as f:
145
+ structure = json.load(f)
146
+
147
+ project_root = os.getcwd()
148
+
149
+ variables = {
150
+ "project_name": project_name,
151
+ }
152
+
153
+ existing_files = [f for f in os.listdir(project_root) if not f.startswith('.')]
154
+ if existing_files:
155
+ print_warning(f"Current directory is not empty. Found {len(existing_files)} files/folders.")
156
+ print_info("Existing items: " + ", ".join(existing_files[:5]) + ("..." if len(existing_files) > 5 else ""))
157
+
158
+ confirm = input(f"{Fore.WHITE}{Style.BRIGHT}❓ Continue anyway? (y/N): {Style.RESET_ALL}").strip().lower()
159
+ if confirm != 'y':
160
+ print_info("Project initialization cancelled.")
161
+ return
162
+
163
+ create_from_json(project_root, structure["{project_name}"], variables)
164
+ print_success(f"React admin dashboard '{project_name}' initialized in current directory: {project_root}")
165
+
166
+ # Ask user for dependency installation
167
+ print(f"\n{Fore.CYAN}{Style.BRIGHT}📦 NODE.JS DEPENDENCY SETUP{Style.RESET_ALL}")
168
+ print("─" * 40)
169
+ print(f"{Fore.YELLOW}{Style.BRIGHT}[1]{Style.RESET_ALL} {Fore.WHITE}Install dependencies now (npm install){Style.RESET_ALL}")
170
+ print(f"{Fore.YELLOW}{Style.BRIGHT}[2]{Style.RESET_ALL} {Fore.WHITE}Use existing node_modules (if available){Style.RESET_ALL}")
171
+ print(f"{Fore.YELLOW}{Style.BRIGHT}[3]{Style.RESET_ALL} {Fore.WHITE}Skip dependency installation{Style.RESET_ALL}")
172
+
173
+ while True:
174
+ deps_choice = input(f"{Fore.WHITE}{Style.BRIGHT}❓ Select dependency option (1-3): {Style.RESET_ALL}").strip()
175
+
176
+ # Clear screen after selection
177
+ os.system('cls' if os.name == 'nt' else 'clear')
178
+
179
+ if deps_choice == "1":
180
+ install_npm_dependencies(project_root)
181
+ break
182
+ elif deps_choice == "2":
183
+ node_modules_path = os.path.join(project_root, "node_modules")
184
+ if os.path.exists(node_modules_path):
185
+ print_success("Found existing node_modules directory!")
186
+ print_info("Using existing dependencies. Run 'npm install' if you need to update them.")
187
+ else:
188
+ print_warning("No existing node_modules found in this location.")
189
+ print_info("You may need to run 'npm install' manually later.")
190
+ break
191
+ elif deps_choice == "3":
192
+ print_info("Skipping dependency installation.")
193
+ print_info("Remember to run 'npm install' before starting development.")
194
+ break
195
+ else:
196
+ print_error("Invalid choice. Please select 1, 2, or 3.")
197
+
198
+ # Show next steps
199
+ print(f"\n{Fore.GREEN}{Style.BRIGHT}🎉 PROJECT READY!{Style.RESET_ALL}")
200
+ print("─" * 30)
201
+ print_info("Next steps:")
202
+ if deps_choice != "1":
203
+ print(" 1. npm install")
204
+ print(" 2. npm run dev")
205
+ else:
206
+ print(" 1. npm run dev")
207
+ print_info("Your React admin dashboard will be available at http://localhost:5173")
208
+ print_info("Make sure your backend API is running on http://localhost:8000")
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "{project_name}",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --host=127.0.0.1",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@tanstack/react-query": "^5.99.0",
14
+ "axios": "^1.13.6",
15
+ "lucide-react": "^0.577.0",
16
+ "react": "^19.2.4",
17
+ "react-dom": "^19.2.4",
18
+ "react-router-dom": "^7.13.1",
19
+ "tailwindcss": "^4.2.2"
20
+ },
21
+ "devDependencies": {
22
+ "@eslint/js": "^9.39.4",
23
+ "@types/node": "^24.12.0",
24
+ "@types/react": "^19.2.14",
25
+ "@types/react-dom": "^19.2.3",
26
+ "@vitejs/plugin-react": "^4.3.4",
27
+ "eslint": "^9.39.4",
28
+ "eslint-plugin-react-hooks": "^7.0.1",
29
+ "eslint-plugin-react-refresh": "^0.5.2",
30
+ "globals": "^17.4.0",
31
+ "typescript": "~5.9.3",
32
+ "typescript-eslint": "^8.56.1",
33
+ "vite": "^5.4.17"
34
+ }
35
+ }
@@ -0,0 +1,60 @@
1
+ import { BrowserRouter, Routes, Route } from 'react-router-dom'
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
3
+ import { ThemeProvider } from '@/context/ThemeContext'
4
+ import { AuthProvider, useAuth } from '@/context/AuthContext'
5
+ import { DashboardLayout } from '@/layouts/DashboardLayout'
6
+ import { AuthLayout } from '@/layouts/AuthLayout'
7
+ import { ProtectedRoute } from '@/components/ProtectedRoute'
8
+ import { DashboardPage } from '@/pages/DashboardPage'
9
+ import { LoginPage } from '@/pages/auth/LoginPage'
10
+ import { RegisterPage } from '@/pages/auth/RegisterPage'
11
+
12
+ const queryClient = new QueryClient({
13
+ defaultOptions: {
14
+ queries: {
15
+ staleTime: 1000 * 60 * 2, // 2 minutes
16
+ gcTime: 1000 * 60 * 10, // 10 minutes
17
+ refetchOnWindowFocus: false,
18
+ retry: 1,
19
+ },
20
+ },
21
+ })
22
+
23
+ function AppShell() {
24
+ return (
25
+ <Routes>
26
+ {/* Auth routes */}
27
+ <Route path="/auth" element={<AuthLayout />}>
28
+ <Route path="login" element={<LoginPage />} />
29
+ <Route path="register" element={<RegisterPage />} />
30
+ </Route>
31
+
32
+ {/* Protected Dashboard routes */}
33
+ <Route
34
+ element={
35
+ <ProtectedRoute>
36
+ <DashboardLayout />
37
+ </ProtectedRoute>
38
+ }
39
+ >
40
+ <Route path="/" element={<DashboardPage />} />
41
+ </Route>
42
+ </Routes>
43
+ )
44
+ }
45
+
46
+ function App() {
47
+ return (
48
+ <QueryClientProvider client={queryClient}>
49
+ <BrowserRouter>
50
+ <ThemeProvider>
51
+ <AuthProvider>
52
+ <AppShell />
53
+ </AuthProvider>
54
+ </ThemeProvider>
55
+ </BrowserRouter>
56
+ </QueryClientProvider>
57
+ )
58
+ }
59
+
60
+ export default App
@@ -0,0 +1,16 @@
1
+ import { Navigate } from 'react-router-dom'
2
+ import { useAuth } from '@/context/AuthContext'
3
+
4
+ export function ProtectedRoute({ children }: { children: React.ReactNode }) {
5
+ const { authStatus } = useAuth()
6
+
7
+ if (authStatus === 'loading') {
8
+ return <div className="flex items-center justify-center min-h-screen">Loading...</div>
9
+ }
10
+
11
+ if (authStatus === 'unauthenticated') {
12
+ return <Navigate to="/auth/login" replace />
13
+ }
14
+
15
+ return <>{children}</>
16
+ }
@@ -0,0 +1,90 @@
1
+ import { NavLink } from 'react-router-dom'
2
+ import { useWorkspace } from '@/context/WorkspaceContext'
3
+ import { useAuth } from '@/context/AuthContext'
4
+ import {
5
+ LayoutDashboard,
6
+ Settings,
7
+ LogOut,
8
+ Menu,
9
+ ChevronRight,
10
+ } from 'lucide-react'
11
+
12
+ interface NavItem {
13
+ to: string
14
+ icon: React.ElementType
15
+ label: string
16
+ }
17
+
18
+ const workspaceNav: NavItem[] = [
19
+ { to: '/', icon: LayoutDashboard, label: 'Dashboard' },
20
+ { to: '/settings', icon: Settings, label: 'Settings' },
21
+ ]
22
+
23
+ export function Sidebar() {
24
+ const { sidebarCollapsed, setSidebarCollapsed } = useWorkspace()
25
+ const { user, logout } = useAuth()
26
+
27
+ return (
28
+ <aside
29
+ className={`fixed left-0 top-0 z-40 flex h-screen flex-col border-r border-gray-200 bg-white transition-all duration-300 ${
30
+ sidebarCollapsed ? 'w-[72px]' : 'w-[260px]'
31
+ }`}
32
+ >
33
+ {/* Logo */}
34
+ <div className="flex h-16 items-center border-b border-gray-200 px-4">
35
+ <button
36
+ onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
37
+ className="rounded-lg p-2 hover:bg-gray-100 transition-colors"
38
+ >
39
+ <Menu className="h-5 w-5 text-gray-600" />
40
+ </button>
41
+ {!sidebarCollapsed && (
42
+ <span className="ml-3 text-lg font-semibold text-gray-800">
43
+ Admin
44
+ </span>
45
+ )}
46
+ </div>
47
+
48
+ {/* Navigation */}
49
+ <nav className="flex-1 overflow-y-auto p-4">
50
+ <ul className="space-y-1">
51
+ {workspaceNav.map((item) => {
52
+ const Icon = item.icon
53
+ return (
54
+ <li key={item.to}>
55
+ <NavLink
56
+ to={item.to}
57
+ className={({ isActive }) =>
58
+ `flex items-center rounded-lg px-3 py-2 transition-colors ${
59
+ isActive
60
+ ? 'bg-blue-50 text-blue-600'
61
+ : 'text-gray-700 hover:bg-gray-100'
62
+ } ${sidebarCollapsed ? 'justify-center' : ''}`
63
+ }
64
+ >
65
+ <Icon className="h-5 w-5 flex-shrink-0" />
66
+ {!sidebarCollapsed && (
67
+ <span className="ml-3">{item.label}</span>
68
+ )}
69
+ </NavLink>
70
+ </li>
71
+ )
72
+ })}
73
+ </ul>
74
+ </nav>
75
+
76
+ {/* User Section */}
77
+ <div className="border-t border-gray-200 p-4">
78
+ <button
79
+ onClick={logout}
80
+ className={`flex items-center rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 transition-colors ${
81
+ sidebarCollapsed ? 'justify-center' : 'w-full'
82
+ }`}
83
+ >
84
+ <LogOut className="h-5 w-5 flex-shrink-0" />
85
+ {!sidebarCollapsed && <span className="ml-3">Logout</span>}
86
+ </button>
87
+ </div>
88
+ </aside>
89
+ )
90
+ }
@@ -0,0 +1,44 @@
1
+ import { useWorkspace } from '@/context/WorkspaceContext'
2
+ import { useAuth } from '@/context/AuthContext'
3
+ import { useTheme } from '@/context/ThemeContext'
4
+ import { User, LogOut, Menu } from 'lucide-react'
5
+
6
+ export function TopNav() {
7
+ const { setSidebarCollapsed } = useWorkspace()
8
+ const { user, logout } = useAuth()
9
+ const { theme, toggleTheme } = useTheme()
10
+
11
+ return (
12
+ <header className="h-16 border-b border-gray-200 bg-white px-6 flex items-center justify-between">
13
+ <button
14
+ onClick={() => setSidebarCollapsed(true)}
15
+ className="lg:hidden p-2 rounded-lg hover:bg-gray-100"
16
+ >
17
+ <Menu className="h-5 w-5 text-gray-600" />
18
+ </button>
19
+
20
+ <div className="flex items-center gap-4">
21
+ <button
22
+ onClick={toggleTheme}
23
+ className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
24
+ title="Toggle theme"
25
+ >
26
+ {theme === 'light' ? '🌙' : '☀️'}
27
+ </button>
28
+
29
+ <div className="flex items-center gap-2">
30
+ <User className="h-5 w-5 text-gray-600" />
31
+ <span className="text-sm text-gray-700">{user?.name || 'User'}</span>
32
+ </div>
33
+
34
+ <button
35
+ onClick={logout}
36
+ className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
37
+ title="Logout"
38
+ >
39
+ <LogOut className="h-5 w-5 text-gray-600" />
40
+ </button>
41
+ </div>
42
+ </header>
43
+ )
44
+ }
@@ -0,0 +1,107 @@
1
+ import { createContext, useContext, useState, useCallback } from 'react'
2
+ import type { ReactNode } from 'react'
3
+ import { useNavigate } from 'react-router-dom'
4
+ import { authAxios } from '@/services/base.service'
5
+ import { authEndpoints } from '@/services/api.service'
6
+ import type { User as ApiUser, AuthResult } from '@/services/api.service'
7
+
8
+ export interface User {
9
+ id: string
10
+ name: string
11
+ email: string
12
+ avatar?: string | null
13
+ role?: string
14
+ created_at?: string
15
+ }
16
+
17
+ export type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated'
18
+
19
+ export interface AuthContextType {
20
+ user: User | null
21
+ accessToken: string | null
22
+ authStatus: AuthStatus
23
+ isAuthenticated: boolean
24
+ isLoading: boolean
25
+
26
+ setAccessToken: (token: string | null) => void
27
+ setUser: (user: User | null) => void
28
+
29
+ handleAuthSuccess: (result: AuthResult) => void
30
+ tryRefreshToken: () => Promise<boolean>
31
+ handleLogout: () => void
32
+ login: (email: string, password: string) => Promise<void>
33
+ register: (name: string, email: string, password: string) => Promise<void>
34
+ logout: () => void
35
+ }
36
+
37
+ const AuthContext = createContext<AuthContextType | undefined>(undefined)
38
+
39
+ export function AuthProvider({ children }: { children: ReactNode }) {
40
+ const [user, setUser] = useState<User | null>(null)
41
+ const [accessToken, setAccessToken] = useState<string | null>(null)
42
+ const [authStatus, setAuthStatus] = useState<AuthStatus>('loading')
43
+ const navigate = useNavigate()
44
+
45
+ const handleAuthSuccess = useCallback((result: AuthResult) => {
46
+ setAccessToken(result.access)
47
+ setUser(result.user)
48
+ setAuthStatus('authenticated')
49
+ }, [])
50
+
51
+ const tryRefreshToken = useCallback(async (): Promise<boolean> => {
52
+ try {
53
+ const result = await authEndpoints.refreshToken(authAxios)
54
+ setAccessToken(result.access)
55
+ setAuthStatus('authenticated')
56
+ return true
57
+ } catch {
58
+ setAuthStatus('unauthenticated')
59
+ setAccessToken(null)
60
+ setUser(null)
61
+ return false
62
+ }
63
+ }, [])
64
+
65
+ const handleLogout = useCallback(() => {
66
+ setAccessToken(null)
67
+ setUser(null)
68
+ setAuthStatus('unauthenticated')
69
+ navigate('/auth/login')
70
+ }, [navigate])
71
+
72
+ const login = useCallback(async (email: string, password: string) => {
73
+ const result = await authEndpoints.login(authAxios, email, password)
74
+ handleAuthSuccess(result)
75
+ }, [handleAuthSuccess])
76
+
77
+ const register = useCallback(async (name: string, email: string, password: string) => {
78
+ const result = await authEndpoints.register(authAxios, name, email, password)
79
+ handleAuthSuccess(result)
80
+ }, [handleAuthSuccess])
81
+
82
+ const value: AuthContextType = {
83
+ user,
84
+ accessToken,
85
+ authStatus,
86
+ isAuthenticated: authStatus === 'authenticated',
87
+ isLoading: authStatus === 'loading',
88
+ setAccessToken,
89
+ setUser,
90
+ handleAuthSuccess,
91
+ tryRefreshToken,
92
+ handleLogout,
93
+ login,
94
+ register,
95
+ logout: handleLogout,
96
+ }
97
+
98
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
99
+ }
100
+
101
+ export function useAuth() {
102
+ const context = useContext(AuthContext)
103
+ if (context === undefined) {
104
+ throw new Error('useAuth must be used within an AuthProvider')
105
+ }
106
+ return context
107
+ }
@@ -0,0 +1,34 @@
1
+ import { createContext, useContext, useState, useCallback } from 'react'
2
+ import type { ReactNode } from 'react'
3
+
4
+ type Theme = 'light' | 'dark'
5
+
6
+ interface ThemeContextType {
7
+ theme: Theme
8
+ toggleTheme: () => void
9
+ }
10
+
11
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
12
+
13
+ export function ThemeProvider({ children }: { children: ReactNode }) {
14
+ const [theme, setTheme] = useState<Theme>('light')
15
+
16
+ const toggleTheme = useCallback(() => {
17
+ setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))
18
+ }, [])
19
+
20
+ const value: ThemeContextType = {
21
+ theme,
22
+ toggleTheme,
23
+ }
24
+
25
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
26
+ }
27
+
28
+ export function useTheme() {
29
+ const context = useContext(ThemeContext)
30
+ if (context === undefined) {
31
+ throw new Error('useTheme must be used within a ThemeProvider')
32
+ }
33
+ return context
34
+ }
@@ -0,0 +1,28 @@
1
+ import { createContext, useContext, useState } from 'react'
2
+ import type { ReactNode } from 'react'
3
+
4
+ interface WorkspaceContextType {
5
+ sidebarCollapsed: boolean
6
+ setSidebarCollapsed: (collapsed: boolean) => void
7
+ }
8
+
9
+ const WorkspaceContext = createContext<WorkspaceContextType | undefined>(undefined)
10
+
11
+ export function WorkspaceProvider({ children }: { children: ReactNode }) {
12
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
13
+
14
+ const value: WorkspaceContextType = {
15
+ sidebarCollapsed,
16
+ setSidebarCollapsed,
17
+ }
18
+
19
+ return <WorkspaceContext.Provider value={value}>{children}</WorkspaceContext.Provider>
20
+ }
21
+
22
+ export function useWorkspace() {
23
+ const context = useContext(WorkspaceContext)
24
+ if (context === undefined) {
25
+ throw new Error('useWorkspace must be used within a WorkspaceProvider')
26
+ }
27
+ return context
28
+ }
@@ -0,0 +1,39 @@
1
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
2
+ import { baseAxios } from '@/services/base.service'
3
+ import { extractErrorMessage } from '@/services/base.service'
4
+
5
+ export function useApi<T>(
6
+ queryKey: string[],
7
+ url: string,
8
+ options?: { enabled?: boolean }
9
+ ) {
10
+ return useQuery({
11
+ queryKey,
12
+ queryFn: async () => {
13
+ const response = await baseAxios.get(url)
14
+ return response.data.data as T
15
+ },
16
+ enabled: options?.enabled,
17
+ })
18
+ }
19
+
20
+ export function useApiMutation<T, V = unknown>(
21
+ url: string,
22
+ method: 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'POST'
23
+ ) {
24
+ const queryClient = useQueryClient()
25
+
26
+ return useMutation({
27
+ mutationFn: async (data?: V) => {
28
+ const response = await baseAxios({
29
+ method,
30
+ url,
31
+ data,
32
+ })
33
+ return response.data.data as T
34
+ },
35
+ onError: (error) => {
36
+ console.error('API mutation error:', extractErrorMessage(error))
37
+ },
38
+ })
39
+ }