agentic-dev 0.2.10 → 0.2.11

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 (291) hide show
  1. package/README.md +23 -9
  2. package/bin/agentic-dev.mjs +656 -124
  3. package/lib/scaffold.mjs +109 -6
  4. package/package.json +1 -1
  5. package/client/admin/.dockerignore +0 -3
  6. package/client/admin/.env.example +0 -1
  7. package/client/admin/Dockerfile +0 -16
  8. package/client/admin/Dockerfile.dev +0 -18
  9. package/client/admin/README.md +0 -20
  10. package/client/admin/index.html +0 -12
  11. package/client/admin/package.json +0 -41
  12. package/client/admin/postcss.config.js +0 -6
  13. package/client/admin/scripts/ui-parity-admin-adapter.mjs +0 -65
  14. package/client/admin/src/api/alerts.ts +0 -33
  15. package/client/admin/src/api/client.ts +0 -71
  16. package/client/admin/src/api/orders.ts +0 -33
  17. package/client/admin/src/api/support.ts +0 -11
  18. package/client/admin/src/app/App.tsx +0 -23
  19. package/client/admin/src/auth/AuthProvider.tsx +0 -122
  20. package/client/admin/src/auth/ProtectedRoute.tsx +0 -22
  21. package/client/admin/src/auth/auth-client.ts +0 -38
  22. package/client/admin/src/auth/types.ts +0 -18
  23. package/client/admin/src/components/AdminNotificationsDrawer.tsx +0 -162
  24. package/client/admin/src/components/AdminShell.tsx +0 -76
  25. package/client/admin/src/components/ui/button.tsx +0 -34
  26. package/client/admin/src/components/ui/input.tsx +0 -21
  27. package/client/admin/src/lib/cn.ts +0 -6
  28. package/client/admin/src/lib/specRouteCatalog.json +0 -30
  29. package/client/admin/src/lib/specScreens.json +0 -22
  30. package/client/admin/src/main.tsx +0 -17
  31. package/client/admin/src/pages/AdminDashboardPage.tsx +0 -171
  32. package/client/admin/src/pages/AdminLoginPage.tsx +0 -75
  33. package/client/admin/src/pages/AdminQueuePage.tsx +0 -107
  34. package/client/admin/src/pages/AdminSupportPage.tsx +0 -61
  35. package/client/admin/src/styles/globals.css +0 -17
  36. package/client/admin/src/theme-vars.ts +0 -18
  37. package/client/admin/src/theme.ts +0 -25
  38. package/client/admin/src/vite-env.d.ts +0 -1
  39. package/client/admin/tailwind.config.js +0 -8
  40. package/client/admin/tsconfig.json +0 -25
  41. package/client/admin/vite.config.ts +0 -12
  42. package/client/landing/.dockerignore +0 -3
  43. package/client/landing/.env.example +0 -1
  44. package/client/landing/Dockerfile +0 -16
  45. package/client/landing/Dockerfile.dev +0 -18
  46. package/client/landing/README.md +0 -18
  47. package/client/landing/index.html +0 -12
  48. package/client/landing/package.json +0 -41
  49. package/client/landing/postcss.config.js +0 -6
  50. package/client/landing/scripts/ui-parity-landing-adapter.mjs +0 -65
  51. package/client/landing/src/App.tsx +0 -21
  52. package/client/landing/src/api/catalog.ts +0 -30
  53. package/client/landing/src/api/client.ts +0 -30
  54. package/client/landing/src/auth/AuthProvider.tsx +0 -122
  55. package/client/landing/src/auth/ProtectedRoute.tsx +0 -22
  56. package/client/landing/src/auth/auth-client.ts +0 -38
  57. package/client/landing/src/auth/types.ts +0 -18
  58. package/client/landing/src/components/LandingShell.tsx +0 -34
  59. package/client/landing/src/lib/specRouteCatalog.json +0 -23
  60. package/client/landing/src/lib/specScreens.json +0 -17
  61. package/client/landing/src/main.tsx +0 -17
  62. package/client/landing/src/pages/LandingHomePage.tsx +0 -215
  63. package/client/landing/src/pages/LandingLoginPage.tsx +0 -90
  64. package/client/landing/src/pages/LandingWorkspacePage.tsx +0 -126
  65. package/client/landing/src/styles/globals.css +0 -17
  66. package/client/landing/src/theme-vars.ts +0 -16
  67. package/client/landing/src/theme.ts +0 -21
  68. package/client/landing/src/vite-env.d.ts +0 -1
  69. package/client/landing/tailwind.config.js +0 -8
  70. package/client/landing/tsconfig.json +0 -25
  71. package/client/landing/vite.config.ts +0 -12
  72. package/client/mobile/.dockerignore +0 -2
  73. package/client/mobile/.env.example +0 -1
  74. package/client/mobile/Dockerfile +0 -16
  75. package/client/mobile/Dockerfile.dev +0 -18
  76. package/client/mobile/README.md +0 -19
  77. package/client/mobile/index.html +0 -12
  78. package/client/mobile/package.json +0 -42
  79. package/client/mobile/postcss.config.js +0 -6
  80. package/client/mobile/scripts/ui-parity-mobile-adapter.mjs +0 -67
  81. package/client/mobile/src/App.tsx +0 -1
  82. package/client/mobile/src/api/client.ts +0 -62
  83. package/client/mobile/src/api/fulfillment.ts +0 -55
  84. package/client/mobile/src/api/shipping.ts +0 -56
  85. package/client/mobile/src/app/App.tsx +0 -23
  86. package/client/mobile/src/auth/AuthProvider.tsx +0 -122
  87. package/client/mobile/src/auth/ProtectedRoute.tsx +0 -27
  88. package/client/mobile/src/auth/auth-client.ts +0 -38
  89. package/client/mobile/src/auth/types.ts +0 -18
  90. package/client/mobile/src/components/InShell.tsx +0 -74
  91. package/client/mobile/src/components/ui/button.tsx +0 -35
  92. package/client/mobile/src/components/ui/card.tsx +0 -15
  93. package/client/mobile/src/components/ui/input.tsx +0 -21
  94. package/client/mobile/src/lib/cn.ts +0 -6
  95. package/client/mobile/src/lib/specRouteCatalog.json +0 -26
  96. package/client/mobile/src/lib/specScreens.json +0 -22
  97. package/client/mobile/src/lib/useSpeechRecognitionInput.ts +0 -271
  98. package/client/mobile/src/main.tsx +0 -17
  99. package/client/mobile/src/pages/DashboardPage.tsx +0 -172
  100. package/client/mobile/src/pages/FulfillmentPage.tsx +0 -138
  101. package/client/mobile/src/pages/LoginPage.tsx +0 -74
  102. package/client/mobile/src/pages/ShippingPage.tsx +0 -338
  103. package/client/mobile/src/styles/globals.css +0 -23
  104. package/client/mobile/src/theme-vars.ts +0 -16
  105. package/client/mobile/src/theme.ts +0 -21
  106. package/client/mobile/src/vite-env.d.ts +0 -1
  107. package/client/mobile/tailwind.config.js +0 -8
  108. package/client/mobile/tsconfig.json +0 -25
  109. package/client/mobile/vite.config.ts +0 -12
  110. package/client/web/.dockerignore +0 -3
  111. package/client/web/.env.example +0 -1
  112. package/client/web/Dockerfile +0 -16
  113. package/client/web/Dockerfile.dev +0 -18
  114. package/client/web/README.md +0 -47
  115. package/client/web/index.html +0 -12
  116. package/client/web/package.json +0 -42
  117. package/client/web/postcss.config.js +0 -6
  118. package/client/web/scripts/ui-parity-web-adapter.mjs +0 -66
  119. package/client/web/src/api/client.ts +0 -30
  120. package/client/web/src/api/orders.ts +0 -42
  121. package/client/web/src/app/App.tsx +0 -21
  122. package/client/web/src/auth/AuthProvider.tsx +0 -122
  123. package/client/web/src/auth/ProtectedRoute.tsx +0 -22
  124. package/client/web/src/auth/auth-client.ts +0 -38
  125. package/client/web/src/auth/types.ts +0 -18
  126. package/client/web/src/components/AppShell.tsx +0 -59
  127. package/client/web/src/components/ui/button.tsx +0 -35
  128. package/client/web/src/components/ui/card.tsx +0 -7
  129. package/client/web/src/components/ui/input.tsx +0 -21
  130. package/client/web/src/lib/cn.ts +0 -6
  131. package/client/web/src/lib/specRouteCatalog.json +0 -23
  132. package/client/web/src/lib/specScreens.json +0 -17
  133. package/client/web/src/main.tsx +0 -17
  134. package/client/web/src/pages/DashboardPage.tsx +0 -158
  135. package/client/web/src/pages/LoginPage.tsx +0 -72
  136. package/client/web/src/pages/OrdersPage.tsx +0 -123
  137. package/client/web/src/styles/globals.css +0 -17
  138. package/client/web/src/theme-vars.ts +0 -18
  139. package/client/web/src/theme.ts +0 -25
  140. package/client/web/src/vite-env.d.ts +0 -1
  141. package/client/web/tailwind.config.js +0 -8
  142. package/client/web/tsconfig.json +0 -25
  143. package/client/web/vite.config.ts +0 -12
  144. package/server/.dockerignore +0 -4
  145. package/server/.env.example +0 -19
  146. package/server/Dockerfile +0 -22
  147. package/server/Dockerfile.dev +0 -19
  148. package/server/README.md +0 -33
  149. package/server/__init__.py +0 -0
  150. package/server/api/__init__.py +0 -1
  151. package/server/api/http/__init__.py +0 -4
  152. package/server/api/http/app.py +0 -53
  153. package/server/api/http/router.py +0 -24
  154. package/server/config.py +0 -52
  155. package/server/contexts/__init__.py +0 -12
  156. package/server/contexts/alerts/__init__.py +0 -1
  157. package/server/contexts/alerts/application/__init__.py +0 -13
  158. package/server/contexts/alerts/application/services.py +0 -41
  159. package/server/contexts/alerts/contracts/__init__.py +0 -3
  160. package/server/contexts/alerts/contracts/http/__init__.py +0 -3
  161. package/server/contexts/alerts/contracts/http/router.py +0 -37
  162. package/server/contexts/alerts/domain/__init__.py +0 -15
  163. package/server/contexts/alerts/domain/models.py +0 -29
  164. package/server/contexts/alerts/infrastructure/__init__.py +0 -11
  165. package/server/contexts/alerts/infrastructure/repository.py +0 -41
  166. package/server/contexts/auth/__init__.py +0 -1
  167. package/server/contexts/auth/application/__init__.py +0 -3
  168. package/server/contexts/auth/application/ports.py +0 -10
  169. package/server/contexts/auth/application/services.py +0 -64
  170. package/server/contexts/auth/contracts/__init__.py +0 -4
  171. package/server/contexts/auth/contracts/http/__init__.py +0 -4
  172. package/server/contexts/auth/contracts/http/dependencies.py +0 -37
  173. package/server/contexts/auth/contracts/http/router.py +0 -19
  174. package/server/contexts/auth/domain/__init__.py +0 -3
  175. package/server/contexts/auth/domain/models.py +0 -24
  176. package/server/contexts/auth/infrastructure/__init__.py +0 -4
  177. package/server/contexts/auth/infrastructure/adapters/memory.py +0 -19
  178. package/server/contexts/auth/infrastructure/adapters/mongodb.py +0 -24
  179. package/server/contexts/auth/infrastructure/adapters/sqlalchemy.py +0 -74
  180. package/server/contexts/auth/infrastructure/repository.py +0 -28
  181. package/server/contexts/catalog/__init__.py +0 -1
  182. package/server/contexts/catalog/application/__init__.py +0 -28
  183. package/server/contexts/catalog/application/ports.py +0 -15
  184. package/server/contexts/catalog/application/services.py +0 -154
  185. package/server/contexts/catalog/contracts/__init__.py +0 -3
  186. package/server/contexts/catalog/contracts/http/__init__.py +0 -3
  187. package/server/contexts/catalog/contracts/http/router.py +0 -60
  188. package/server/contexts/catalog/domain/__init__.py +0 -45
  189. package/server/contexts/catalog/domain/models.py +0 -113
  190. package/server/contexts/catalog/infrastructure/__init__.py +0 -4
  191. package/server/contexts/catalog/infrastructure/adapters/memory.py +0 -62
  192. package/server/contexts/catalog/infrastructure/repository.py +0 -8
  193. package/server/contexts/fulfillment/__init__.py +0 -1
  194. package/server/contexts/fulfillment/application/__init__.py +0 -13
  195. package/server/contexts/fulfillment/application/ports.py +0 -20
  196. package/server/contexts/fulfillment/application/services.py +0 -85
  197. package/server/contexts/fulfillment/contracts/__init__.py +0 -3
  198. package/server/contexts/fulfillment/contracts/http/__init__.py +0 -3
  199. package/server/contexts/fulfillment/contracts/http/router.py +0 -40
  200. package/server/contexts/fulfillment/domain/__init__.py +0 -25
  201. package/server/contexts/fulfillment/domain/models.py +0 -73
  202. package/server/contexts/fulfillment/infrastructure/__init__.py +0 -13
  203. package/server/contexts/fulfillment/infrastructure/adapters/memory.py +0 -43
  204. package/server/contexts/fulfillment/infrastructure/repository.py +0 -97
  205. package/server/contexts/health/__init__.py +0 -1
  206. package/server/contexts/health/application/__init__.py +0 -3
  207. package/server/contexts/health/application/services.py +0 -2
  208. package/server/contexts/health/contracts/__init__.py +0 -3
  209. package/server/contexts/health/contracts/http/__init__.py +0 -3
  210. package/server/contexts/health/contracts/http/router.py +0 -10
  211. package/server/contexts/inventory/__init__.py +0 -1
  212. package/server/contexts/inventory/application/__init__.py +0 -28
  213. package/server/contexts/inventory/application/ports.py +0 -11
  214. package/server/contexts/inventory/application/services.py +0 -214
  215. package/server/contexts/inventory/contracts/__init__.py +0 -3
  216. package/server/contexts/inventory/contracts/http/__init__.py +0 -3
  217. package/server/contexts/inventory/contracts/http/router.py +0 -82
  218. package/server/contexts/inventory/domain/__init__.py +0 -33
  219. package/server/contexts/inventory/domain/models.py +0 -93
  220. package/server/contexts/inventory/infrastructure/__init__.py +0 -4
  221. package/server/contexts/inventory/infrastructure/adapters/memory.py +0 -24
  222. package/server/contexts/inventory/infrastructure/repository.py +0 -8
  223. package/server/contexts/orders/__init__.py +0 -1
  224. package/server/contexts/orders/application/__init__.py +0 -19
  225. package/server/contexts/orders/application/services.py +0 -127
  226. package/server/contexts/orders/contracts/__init__.py +0 -3
  227. package/server/contexts/orders/contracts/http/__init__.py +0 -3
  228. package/server/contexts/orders/contracts/http/router.py +0 -82
  229. package/server/contexts/orders/domain/__init__.py +0 -29
  230. package/server/contexts/orders/domain/models.py +0 -95
  231. package/server/contexts/orders/infrastructure/__init__.py +0 -7
  232. package/server/contexts/orders/infrastructure/repository.py +0 -104
  233. package/server/contexts/shipping/__init__.py +0 -1
  234. package/server/contexts/shipping/application/__init__.py +0 -13
  235. package/server/contexts/shipping/application/services.py +0 -92
  236. package/server/contexts/shipping/contracts/__init__.py +0 -3
  237. package/server/contexts/shipping/contracts/http/__init__.py +0 -3
  238. package/server/contexts/shipping/contracts/http/router.py +0 -40
  239. package/server/contexts/shipping/domain/__init__.py +0 -19
  240. package/server/contexts/shipping/domain/models.py +0 -48
  241. package/server/contexts/shipping/infrastructure/__init__.py +0 -9
  242. package/server/contexts/shipping/infrastructure/repository.py +0 -50
  243. package/server/contexts/support/__init__.py +0 -1
  244. package/server/contexts/support/application/__init__.py +0 -13
  245. package/server/contexts/support/application/services.py +0 -29
  246. package/server/contexts/support/contracts/__init__.py +0 -3
  247. package/server/contexts/support/contracts/http/__init__.py +0 -3
  248. package/server/contexts/support/contracts/http/router.py +0 -40
  249. package/server/contexts/support/domain/__init__.py +0 -13
  250. package/server/contexts/support/domain/models.py +0 -27
  251. package/server/contexts/support/infrastructure/__init__.py +0 -11
  252. package/server/contexts/support/infrastructure/repository.py +0 -70
  253. package/server/contexts/user/__init__.py +0 -1
  254. package/server/contexts/user/application/__init__.py +0 -3
  255. package/server/contexts/user/application/ports.py +0 -11
  256. package/server/contexts/user/application/services.py +0 -44
  257. package/server/contexts/user/contracts/__init__.py +0 -3
  258. package/server/contexts/user/contracts/http/__init__.py +0 -3
  259. package/server/contexts/user/contracts/http/router.py +0 -26
  260. package/server/contexts/user/domain/__init__.py +0 -3
  261. package/server/contexts/user/domain/models.py +0 -22
  262. package/server/contexts/user/infrastructure/__init__.py +0 -3
  263. package/server/contexts/user/infrastructure/adapters/memory.py +0 -27
  264. package/server/contexts/user/infrastructure/adapters/mongodb.py +0 -41
  265. package/server/contexts/user/infrastructure/adapters/sqlalchemy.py +0 -94
  266. package/server/contexts/user/infrastructure/factory.py +0 -28
  267. package/server/data/README.md +0 -24
  268. package/server/data/bootstrap/alerts.json +0 -38
  269. package/server/data/bootstrap/auth_accounts.json +0 -18
  270. package/server/data/bootstrap/catalog_products.json +0 -179
  271. package/server/data/bootstrap/fulfillment_events.json +0 -5
  272. package/server/data/bootstrap/fulfillment_notes.json +0 -5
  273. package/server/data/bootstrap/fulfillment_tasks.json +0 -50
  274. package/server/data/bootstrap/inventory_levels.json +0 -80
  275. package/server/data/bootstrap/orders.json +0 -62
  276. package/server/data/bootstrap/shipping_shipments.json +0 -50
  277. package/server/data/bootstrap/support_faqs.json +0 -26
  278. package/server/data/bootstrap/users.json +0 -20
  279. package/server/data/bootstrap_loader.py +0 -15
  280. package/server/docker-entrypoint.sh +0 -56
  281. package/server/main.py +0 -3
  282. package/server/pyproject.toml +0 -36
  283. package/server/shared/__init__.py +0 -1
  284. package/server/shared/application/__init__.py +0 -3
  285. package/server/shared/application/health.py +0 -2
  286. package/server/shared/infrastructure/__init__.py +0 -10
  287. package/server/shared/infrastructure/runtime.py +0 -6
  288. package/server/shared/infrastructure/security.py +0 -33
  289. package/server/tests/e2e/test_domain_feature_flows.py +0 -483
  290. package/server/tests/test_health.py +0 -49
  291. package/server/uv.lock +0 -1169
@@ -1,22 +0,0 @@
1
- import { Navigate, Outlet, useLocation } from "react-router-dom";
2
-
3
- import { useAuth } from "@/auth/AuthProvider";
4
-
5
- export function ProtectedRoute() {
6
- const { initializing, user } = useAuth();
7
- const location = useLocation();
8
-
9
- if (initializing) {
10
- return (
11
- <div className="flex min-h-screen items-center justify-center bg-[var(--admin-shell-bg)] text-sm font-medium text-[#5d6879]">
12
- 관리자 세션을 확인하는 중입니다.
13
- </div>
14
- );
15
- }
16
-
17
- if (!user) {
18
- return <Navigate to="/login" replace state={{ from: location }} />;
19
- }
20
-
21
- return <Outlet />;
22
- }
@@ -1,38 +0,0 @@
1
- import type { AuthToken, AuthUser, LoginCommand } from "@/auth/types";
2
-
3
- const API_BASE_URL = (import.meta.env.VITE_API_BASE_URL as string | undefined)?.replace(/\/$/, "") ?? "";
4
-
5
- async function readJson<T>(response: Response): Promise<T> {
6
- if (!response.ok) {
7
- let message = "Request failed";
8
- try {
9
- const data = (await response.json()) as { detail?: string };
10
- if (typeof data.detail === "string" && data.detail.length > 0) {
11
- message = data.detail;
12
- }
13
- } catch {
14
- message = response.statusText || message;
15
- }
16
- throw new Error(message);
17
- }
18
-
19
- return (await response.json()) as T;
20
- }
21
-
22
- export async function login(command: LoginCommand): Promise<AuthToken> {
23
- const response = await fetch(`${API_BASE_URL}/auth/login`, {
24
- method: "POST",
25
- headers: { "Content-Type": "application/json" },
26
- body: JSON.stringify(command),
27
- });
28
-
29
- return readJson<AuthToken>(response);
30
- }
31
-
32
- export async function getMe(accessToken: string): Promise<AuthUser> {
33
- const response = await fetch(`${API_BASE_URL}/auth/me`, {
34
- headers: { Authorization: `Bearer ${accessToken}` },
35
- });
36
-
37
- return readJson<AuthUser>(response);
38
- }
@@ -1,18 +0,0 @@
1
- export interface AuthToken {
2
- access_token: string;
3
- token_type: string;
4
- user_id: string;
5
- }
6
-
7
- export interface AuthUser {
8
- id: string;
9
- name: string;
10
- email: string;
11
- role: string;
12
- status: string;
13
- }
14
-
15
- export interface LoginCommand {
16
- email: string;
17
- password: string;
18
- }
@@ -1,162 +0,0 @@
1
- import { Check, CircleDot, X } from "lucide-react";
2
- import { useEffect, useState } from "react";
3
-
4
- import {
5
- fetchAlerts,
6
- markAlertRead,
7
- markAllAlertsRead,
8
- type AlertItem,
9
- } from "@/api/alerts";
10
- import { useAuth } from "@/auth/AuthProvider";
11
- import { Button } from "@/components/ui/button";
12
-
13
- export function AdminNotificationsDrawer({
14
- open,
15
- onClose,
16
- }: {
17
- open: boolean;
18
- onClose: () => void;
19
- }) {
20
- const { token } = useAuth();
21
- const [items, setItems] = useState<AlertItem[]>([]);
22
- const [loading, setLoading] = useState(false);
23
- const [error, setError] = useState<string | null>(null);
24
- const [unreadCount, setUnreadCount] = useState(0);
25
-
26
- useEffect(() => {
27
- if (!open || !token?.access_token) {
28
- return;
29
- }
30
-
31
- let cancelled = false;
32
- setLoading(true);
33
- setError(null);
34
-
35
- fetchAlerts(token.access_token)
36
- .then((response) => {
37
- if (!cancelled) {
38
- setItems(response.items);
39
- setUnreadCount(response.unread_count);
40
- }
41
- })
42
- .catch((nextError: Error) => {
43
- if (!cancelled) {
44
- setError(nextError.message);
45
- setItems([]);
46
- setUnreadCount(0);
47
- }
48
- })
49
- .finally(() => {
50
- if (!cancelled) {
51
- setLoading(false);
52
- }
53
- });
54
-
55
- return () => {
56
- cancelled = true;
57
- };
58
- }, [open, token?.access_token]);
59
-
60
- if (!open) return null;
61
-
62
- async function handleMarkRead(alertId: string) {
63
- if (!token?.access_token) {
64
- return;
65
- }
66
-
67
- const updated = await markAlertRead(alertId, token.access_token);
68
- setItems((current) =>
69
- current.map((item) => (item.id === updated.id ? updated : item)),
70
- );
71
- setUnreadCount((current) => Math.max(0, updated.read ? current - 1 : current));
72
- }
73
-
74
- async function handleMarkAllRead() {
75
- if (!token?.access_token) {
76
- return;
77
- }
78
-
79
- const result = await markAllAlertsRead(token.access_token);
80
- setItems((current) => current.map((item) => ({ ...item, read: true })));
81
- setUnreadCount(result.unread_count);
82
- }
83
-
84
- return (
85
- <div className="fixed inset-0 z-50 bg-black/20">
86
- <div className="ml-auto flex h-full w-[var(--admin-drawer-width)] flex-col border-l border-[var(--admin-border)] bg-white shadow-2xl">
87
- <div className="flex items-center justify-between border-b border-[var(--admin-border)] px-6 py-5">
88
- <div>
89
- <h2 className="text-lg font-bold text-[#1f2634]">알림</h2>
90
- <p className="mt-1 text-sm text-[var(--admin-muted)]">
91
- 운영 이벤트와 시스템 상태 · 미읽음 {unreadCount}건
92
- </p>
93
- </div>
94
- <button
95
- className="inline-flex h-10 w-10 items-center justify-center rounded-full border border-[var(--admin-border)]"
96
- onClick={onClose}
97
- >
98
- <X className="h-4 w-4" />
99
- </button>
100
- </div>
101
- <div className="flex-1 space-y-3 overflow-y-auto p-6">
102
- {loading ? (
103
- <div className="text-sm text-[var(--admin-muted)]">
104
- 알림을 불러오는 중입니다.
105
- </div>
106
- ) : null}
107
- {error ? (
108
- <div className="text-sm font-semibold text-[var(--admin-danger)]">
109
- {error}
110
- </div>
111
- ) : null}
112
- {!loading && !error && !items.length ? (
113
- <div className="text-sm text-[var(--admin-muted)]">
114
- 표시할 알림이 없습니다.
115
- </div>
116
- ) : null}
117
- {items.map((item) => (
118
- <div
119
- key={item.id}
120
- className="rounded-2xl border border-[var(--admin-border)] bg-[#fafbfd] p-4"
121
- >
122
- <div className="flex items-start justify-between gap-3">
123
- <div>
124
- <p className="font-semibold text-[#202939]">{item.title}</p>
125
- <p className="mt-1 text-sm text-[var(--admin-muted)]">
126
- {item.message}
127
- </p>
128
- <p className="mt-2 text-xs text-[var(--admin-muted)]">
129
- {item.source} · {item.created_at}
130
- </p>
131
- </div>
132
- {!item.read ? (
133
- <button
134
- className="inline-flex items-center gap-1 rounded-full bg-white px-3 py-1 text-xs font-semibold text-[#344156]"
135
- onClick={() => void handleMarkRead(item.id)}
136
- >
137
- <CircleDot className="h-3.5 w-3.5" />
138
- 읽음
139
- </button>
140
- ) : (
141
- <span className="rounded-full bg-white px-3 py-1 text-xs font-semibold text-[var(--admin-muted)]">
142
- 읽음
143
- </span>
144
- )}
145
- </div>
146
- </div>
147
- ))}
148
- </div>
149
- <div className="border-t border-[var(--admin-border)] p-6">
150
- <Button
151
- variant="outline"
152
- className="w-full justify-center gap-2"
153
- onClick={() => void handleMarkAllRead()}
154
- >
155
- <Check className="h-4 w-4" />
156
- 모두 읽음
157
- </Button>
158
- </div>
159
- </div>
160
- </div>
161
- );
162
- }
@@ -1,76 +0,0 @@
1
- import { Bell, LayoutDashboard, LifeBuoy, Settings2 } from "lucide-react";
2
- import { useState } from "react";
3
- import { NavLink, Outlet } from "react-router-dom";
4
-
5
- import { useAuth } from "@/auth/AuthProvider";
6
- import { AdminNotificationsDrawer } from "@/components/AdminNotificationsDrawer";
7
- import { Button } from "@/components/ui/button";
8
- import { adminTheme } from "@/theme";
9
- import { adminThemeVars } from "@/theme-vars";
10
-
11
- const navItems = [
12
- { to: "/", label: "대시보드", icon: LayoutDashboard },
13
- { to: "/queue", label: "거래 관리", icon: Settings2 },
14
- { to: "/support", label: "고객지원", icon: LifeBuoy },
15
- ];
16
-
17
- export function AdminShell() {
18
- const [notificationsOpen, setNotificationsOpen] = useState(false);
19
- const { logout, user } = useAuth();
20
-
21
- return (
22
- <div className="min-h-screen bg-[var(--admin-shell-bg)] text-[#283042]" style={adminThemeVars(adminTheme)}>
23
- <div className="grid min-h-screen grid-cols-[var(--admin-rail-width)_1fr]">
24
- <aside className="bg-[var(--admin-rail-bg)] px-6 py-8 text-white">
25
- <div className="pb-8 text-[22px] font-black tracking-[0.18em]">ADMIN</div>
26
- <nav className="space-y-2">
27
- {navItems.map((item) => {
28
- const Icon = item.icon;
29
- return (
30
- <NavLink
31
- key={item.to}
32
- to={item.to}
33
- end={item.to === "/"}
34
- className={({ isActive }) =>
35
- `flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-semibold ${isActive ? "bg-white/14 text-white" : "text-white/70"}`
36
- }
37
- >
38
- <Icon className="h-4 w-4" />
39
- {item.label}
40
- </NavLink>
41
- );
42
- })}
43
- </nav>
44
- </aside>
45
-
46
- <div className="min-w-0">
47
- <header className="flex h-[var(--admin-topbar-height)] items-center justify-between border-b border-[var(--admin-border)] bg-white px-8">
48
- <div>
49
- <p className="text-sm text-[var(--admin-muted)]">운영 콘솔</p>
50
- <p className="mt-1 text-lg font-bold">제품 우선 관리자 화면</p>
51
- </div>
52
- <div className="flex items-center gap-3">
53
- <button
54
- aria-label="알림 열기"
55
- className="inline-flex h-10 w-10 items-center justify-center rounded-full border border-[var(--admin-border)] bg-white"
56
- onClick={() => setNotificationsOpen(true)}
57
- >
58
- <Bell className="h-4 w-4 text-[#5d6879]" />
59
- </button>
60
- <div className="rounded-full bg-[#f7f8fb] px-4 py-2 text-sm font-semibold">{user?.name ?? user?.email}</div>
61
- <Button variant="outline" onClick={logout}>
62
- 로그아웃
63
- </Button>
64
- </div>
65
- </header>
66
-
67
- <main className="p-8">
68
- <Outlet />
69
- </main>
70
- </div>
71
- </div>
72
-
73
- <AdminNotificationsDrawer open={notificationsOpen} onClose={() => setNotificationsOpen(false)} />
74
- </div>
75
- );
76
- }
@@ -1,34 +0,0 @@
1
- import * as React from "react";
2
- import { cva, type VariantProps } from "class-variance-authority";
3
-
4
- import { cn } from "@/lib/cn";
5
-
6
- const buttonVariants = cva(
7
- "inline-flex items-center justify-center rounded-md text-sm font-semibold transition-colors disabled:pointer-events-none disabled:opacity-50",
8
- {
9
- variants: {
10
- variant: {
11
- primary: "bg-[#f0447d] text-white hover:bg-[#d4386b]",
12
- neutral: "bg-[#232a39] text-white hover:bg-[#151b28]",
13
- outline: "border border-[var(--admin-border)] bg-white text-[#3a4354] hover:bg-[#f7f8fb]",
14
- },
15
- size: {
16
- default: "h-10 px-4",
17
- sm: "h-9 px-3",
18
- },
19
- },
20
- defaultVariants: {
21
- variant: "primary",
22
- size: "default",
23
- },
24
- },
25
- );
26
-
27
- export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {}
28
-
29
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, ...props }, ref) => {
30
- return <button ref={ref} className={cn(buttonVariants({ variant, size }), className)} {...props} />;
31
- });
32
- Button.displayName = "Button";
33
-
34
- export { Button, buttonVariants };
@@ -1,21 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "@/lib/cn";
4
-
5
- export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
6
-
7
- const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
8
- return (
9
- <input
10
- ref={ref}
11
- className={cn(
12
- "flex h-11 w-full rounded-md border border-[var(--admin-border)] bg-white px-3 py-2 text-sm text-[#2a3140] outline-none placeholder:text-[#8a93a3] focus:ring-2 focus:ring-[#f5a7c1]",
13
- className,
14
- )}
15
- {...props}
16
- />
17
- );
18
- });
19
- Input.displayName = "Input";
20
-
21
- export { Input };
@@ -1,6 +0,0 @@
1
- import { clsx, type ClassValue } from "clsx";
2
- import { twMerge } from "tailwind-merge";
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
@@ -1,30 +0,0 @@
1
- [
2
- {
3
- "id": "TADM_001",
4
- "route": "/",
5
- "binding": "direct",
6
- "notes": ["Primary admin dashboard surface."],
7
- "tags": ["shell"]
8
- },
9
- {
10
- "id": "TADM_002",
11
- "route": "/queue",
12
- "binding": "direct",
13
- "notes": ["Admin operations queue view."],
14
- "tags": ["queue"]
15
- },
16
- {
17
- "id": "TADM_003",
18
- "route": "/support",
19
- "binding": "direct",
20
- "notes": ["Support workflow and response workspace."],
21
- "tags": ["support"]
22
- },
23
- {
24
- "id": "TADM_004",
25
- "route": "/login",
26
- "binding": "direct",
27
- "notes": ["Public admin authentication route."],
28
- "tags": ["public"]
29
- }
30
- ]
@@ -1,22 +0,0 @@
1
- [
2
- {
3
- "id": "TADM_001",
4
- "title": "Admin Dashboard",
5
- "tags": ["admin", "overview"]
6
- },
7
- {
8
- "id": "TADM_002",
9
- "title": "Admin Queue",
10
- "tags": ["admin", "operations"]
11
- },
12
- {
13
- "id": "TADM_003",
14
- "title": "Admin Support",
15
- "tags": ["admin", "support"]
16
- },
17
- {
18
- "id": "TADM_004",
19
- "title": "Admin Login",
20
- "tags": ["auth"]
21
- }
22
- ]
@@ -1,17 +0,0 @@
1
- import React from "react";
2
- import ReactDOM from "react-dom/client";
3
- import { BrowserRouter } from "react-router-dom";
4
-
5
- import { AuthProvider } from "@/auth/AuthProvider";
6
- import { App } from "./app/App";
7
- import "./styles/globals.css";
8
-
9
- ReactDOM.createRoot(document.getElementById("root")!).render(
10
- <React.StrictMode>
11
- <AuthProvider>
12
- <BrowserRouter>
13
- <App />
14
- </BrowserRouter>
15
- </AuthProvider>
16
- </React.StrictMode>,
17
- );
@@ -1,171 +0,0 @@
1
- import { useEffect, useState } from "react";
2
-
3
- import { fetchAlerts, type AlertItem, type AlertsPayload } from "@/api/alerts";
4
- import { fetchAdminOverview, type AdminOverviewResponse } from "@/api/orders";
5
- import { useAuth } from "@/auth/AuthProvider";
6
-
7
- function alertToneClassName(tone: string) {
8
- if (tone === "danger") {
9
- return "bg-[#fff3f5] text-[#8a274b]";
10
- }
11
- if (tone === "warning") {
12
- return "bg-[#fff4df] text-[#9a5b20]";
13
- }
14
-
15
- return "bg-[#f5f7fb] text-[#344156]";
16
- }
17
-
18
- export function AdminDashboardPage() {
19
- const { token } = useAuth();
20
- const [overview, setOverview] = useState<AdminOverviewResponse | null>(null);
21
- const [alerts, setAlerts] = useState<AlertsPayload | null>(null);
22
- const [loading, setLoading] = useState(true);
23
- const [error, setError] = useState<string | null>(null);
24
-
25
- useEffect(() => {
26
- if (!token?.access_token) {
27
- return;
28
- }
29
-
30
- let cancelled = false;
31
-
32
- setLoading(true);
33
- setError(null);
34
-
35
- Promise.all([
36
- fetchAdminOverview(token.access_token),
37
- fetchAlerts(token.access_token),
38
- ])
39
- .then(([overviewResponse, alertsResponse]) => {
40
- if (!cancelled) {
41
- setOverview(overviewResponse);
42
- setAlerts(alertsResponse);
43
- }
44
- })
45
- .catch((nextError: Error) => {
46
- if (!cancelled) {
47
- setError(nextError.message);
48
- setOverview(null);
49
- setAlerts(null);
50
- }
51
- })
52
- .finally(() => {
53
- if (!cancelled) {
54
- setLoading(false);
55
- }
56
- });
57
-
58
- return () => {
59
- cancelled = true;
60
- };
61
- }, [token?.access_token]);
62
-
63
- return (
64
- <div className="space-y-6">
65
- <section className="grid gap-4 md:grid-cols-3">
66
- {(overview?.cards ?? []).map((card) => (
67
- <div
68
- key={card.label}
69
- className="rounded-[var(--admin-card-radius)] border border-[var(--admin-border)] bg-white p-6"
70
- >
71
- <p className="text-sm text-[var(--admin-muted)]">{card.label}</p>
72
- <p
73
- className={`mt-3 text-3xl font-bold text-[#202939] ${card.tone ?? ""}`}
74
- >
75
- {card.value}
76
- </p>
77
- </div>
78
- ))}
79
- {loading && !overview ? (
80
- <div className="rounded-[var(--admin-card-radius)] border border-[var(--admin-border)] bg-white p-6 md:col-span-3">
81
- <p className="text-sm text-[var(--admin-muted)]">운영 요약</p>
82
- <p className="mt-3 text-lg font-semibold text-[#202939]">
83
- 데이터를 불러오는 중입니다.
84
- </p>
85
- </div>
86
- ) : null}
87
- {error ? (
88
- <div className="rounded-[var(--admin-card-radius)] border border-[var(--admin-border)] bg-white p-6 md:col-span-3">
89
- <p className="text-sm text-[var(--admin-muted)]">운영 요약</p>
90
- <p className="mt-3 text-lg font-semibold text-[var(--admin-danger)]">
91
- {error}
92
- </p>
93
- </div>
94
- ) : null}
95
- </section>
96
-
97
- <section className="grid gap-6 xl:grid-cols-[1.1fr_0.9fr]">
98
- <div className="rounded-[var(--admin-card-radius)] border border-[var(--admin-border)] bg-white p-6">
99
- <h2 className="text-lg font-bold">거래 상태별 현황</h2>
100
- <div className="mt-6 grid gap-3 text-sm">
101
- {loading && !(overview?.stage_statuses.length ?? 0) ? (
102
- <div className="text-[var(--admin-muted)]">
103
- 상태별 현황을 불러오는 중입니다.
104
- </div>
105
- ) : null}
106
- {error ? (
107
- <div className="text-[var(--admin-danger)]">{error}</div>
108
- ) : null}
109
- {!loading && !error && !(overview?.stage_statuses.length ?? 0) ? (
110
- <div className="text-[var(--admin-muted)]">
111
- 표시할 상태 데이터가 없습니다.
112
- </div>
113
- ) : null}
114
- {(overview?.stage_statuses ?? []).map(({ label, value }) => (
115
- <div
116
- key={label}
117
- className="flex items-center justify-between rounded-xl bg-[#f7f8fb] px-4 py-3"
118
- >
119
- <span className="text-[#344156]">{label}</span>
120
- <span className="font-semibold">{value}</span>
121
- </div>
122
- ))}
123
- </div>
124
- </div>
125
-
126
- <div className="rounded-[var(--admin-card-radius)] border border-[var(--admin-border)] bg-white p-6">
127
- <div className="flex items-center justify-between gap-3">
128
- <h2 className="text-lg font-bold">운영 알림</h2>
129
- <span className="rounded-full bg-[#f5f7fb] px-3 py-1 text-xs font-semibold text-[#5d6879]">
130
- 미읽음 {alerts?.unread_count ?? 0}
131
- </span>
132
- </div>
133
- <div className="mt-6 space-y-3 text-sm">
134
- {loading && !(alerts?.items.length ?? 0) ? (
135
- <div className="text-[var(--admin-muted)]">
136
- 운영 알림을 불러오는 중입니다.
137
- </div>
138
- ) : null}
139
- {error ? (
140
- <div className="text-[var(--admin-danger)]">{error}</div>
141
- ) : null}
142
- {!loading && !error && !(alerts?.items.length ?? 0) ? (
143
- <div className="text-[var(--admin-muted)]">
144
- 운영 알림이 없습니다.
145
- </div>
146
- ) : null}
147
- {(alerts?.items ?? []).slice(0, 4).map((alert: AlertItem) => (
148
- <div
149
- key={alert.id}
150
- className={`rounded-xl px-4 py-3 ${alertToneClassName(alert.tone)}`}
151
- >
152
- <div className="flex items-center justify-between gap-3">
153
- <span className="font-semibold">{alert.title}</span>
154
- {!alert.read ? (
155
- <span className="rounded-full bg-white/70 px-2 py-1 text-[10px] font-bold">
156
- NEW
157
- </span>
158
- ) : null}
159
- </div>
160
- <p className="mt-2">{alert.message}</p>
161
- <p className="mt-2 text-xs opacity-70">
162
- {alert.source} · {alert.created_at}
163
- </p>
164
- </div>
165
- ))}
166
- </div>
167
- </div>
168
- </section>
169
- </div>
170
- );
171
- }