crewos 0.1.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 (64) hide show
  1. package/app/.env.example +1 -0
  2. package/app/index.html +50 -0
  3. package/app/package.json +25 -0
  4. package/app/public/favicon.svg +1 -0
  5. package/app/public/images/cursor-ide-guiiding.png +0 -0
  6. package/app/public/images/gpt.jpg +0 -0
  7. package/app/src/app.jsx +22 -0
  8. package/app/src/components/ConfirmModal.jsx +50 -0
  9. package/app/src/components/Icons.jsx +377 -0
  10. package/app/src/components/RedirectRoute.jsx +14 -0
  11. package/app/src/components/SplashScreen.jsx +15 -0
  12. package/app/src/hooks/useAuth.js +28 -0
  13. package/app/src/index.css +268 -0
  14. package/app/src/main.jsx +5 -0
  15. package/app/src/navigations/AuthRoutes.jsx +15 -0
  16. package/app/src/navigations/MainRoutes.jsx +15 -0
  17. package/app/src/navigations/OnboardingRoutes.jsx +15 -0
  18. package/app/src/navigations/index.jsx +37 -0
  19. package/app/src/pages/Home/index.jsx +2095 -0
  20. package/app/src/pages/Login/index.jsx +118 -0
  21. package/app/src/pages/Onboarding/index.jsx +550 -0
  22. package/app/src/services/api.js +46 -0
  23. package/app/src/services/auth.service.js +3 -0
  24. package/app/src/services/config.service.js +13 -0
  25. package/app/src/services/member.service.js +7 -0
  26. package/app/src/services/onboarding.service.js +17 -0
  27. package/app/src/services/role.service.js +6 -0
  28. package/app/src/services/task.service.js +22 -0
  29. package/app/src/stores/auth.store.js +7 -0
  30. package/app/src/utils/environments.js +5 -0
  31. package/app/vite.config.js +10 -0
  32. package/app/yarn.lock +1337 -0
  33. package/backend/package-lock.json +918 -0
  34. package/backend/package.json +18 -0
  35. package/backend/src/configs/db.config.js +40 -0
  36. package/backend/src/controllers/auth.controller.js +19 -0
  37. package/backend/src/controllers/config.controller.js +23 -0
  38. package/backend/src/controllers/member.controller.js +30 -0
  39. package/backend/src/controllers/models.controller.js +25 -0
  40. package/backend/src/controllers/onboarding.controller.js +49 -0
  41. package/backend/src/controllers/role.controller.js +17 -0
  42. package/backend/src/controllers/task.controller.js +63 -0
  43. package/backend/src/index.js +36 -0
  44. package/backend/src/middlewares/onboarding.guard.js +14 -0
  45. package/backend/src/routes/auth.route.js +8 -0
  46. package/backend/src/routes/config.route.js +11 -0
  47. package/backend/src/routes/index.js +22 -0
  48. package/backend/src/routes/member.route.js +11 -0
  49. package/backend/src/routes/models.route.js +8 -0
  50. package/backend/src/routes/onboarding.route.js +13 -0
  51. package/backend/src/routes/role.route.js +9 -0
  52. package/backend/src/routes/task.route.js +20 -0
  53. package/backend/src/services/auth.service.js +14 -0
  54. package/backend/src/services/config.service.js +176 -0
  55. package/backend/src/services/data/roles.json +474 -0
  56. package/backend/src/services/member.service.js +77 -0
  57. package/backend/src/services/onboarding.service.js +328 -0
  58. package/backend/src/services/role.service.js +23 -0
  59. package/backend/src/services/task.service.js +665 -0
  60. package/backend/src/utils/catcher.js +9 -0
  61. package/backend/src/utils/sanitize.js +13 -0
  62. package/backend/yarn.lock +513 -0
  63. package/bin/crewos.js +307 -0
  64. package/package.json +11 -0
@@ -0,0 +1 @@
1
+ VITE_BACKEND_URL=
package/app/index.html ADDED
@@ -0,0 +1,50 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <link rel="apple-touch-icon" href="/favicon.svg" />
7
+ <meta name="theme-color" content="#08110c" />
8
+ <meta name="application-name" content="crewOS" />
9
+ <meta name="apple-mobile-web-app-title" content="crewOS" />
10
+ <meta
11
+ name="description"
12
+ content="crewOS - The operating system for AI-powered development teams."
13
+ />
14
+ <meta name="robots" content="index,follow" />
15
+ <meta
16
+ name="keywords"
17
+ content="crewOS, AI development, team collaboration, task management, developer tools"
18
+ />
19
+
20
+ <meta property="og:type" content="website" />
21
+ <meta property="og:site_name" content="crewOS" />
22
+ <meta property="og:title" content="crewOS" />
23
+ <meta
24
+ property="og:description"
25
+ content="The operating system for AI-powered development teams."
26
+ />
27
+ <meta property="og:image" content="/favicon.svg" />
28
+
29
+ <meta name="twitter:card" content="summary_large_image" />
30
+ <meta name="twitter:title" content="crewOS" />
31
+ <meta
32
+ name="twitter:description"
33
+ content="The operating system for AI-powered development teams."
34
+ />
35
+ <meta name="twitter:image" content="/favicon.svg" />
36
+
37
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
38
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
39
+ <link
40
+ href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;500;600;700&display=swap"
41
+ rel="stylesheet"
42
+ />
43
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
44
+ <title>crewOS</title>
45
+ </head>
46
+ <body>
47
+ <div id="app"></div>
48
+ <script type="module" src="/src/main.jsx"></script>
49
+ </body>
50
+ </html>
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "crewos-app",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "preview": "vite preview",
9
+ "build": "vite build"
10
+ },
11
+ "dependencies": {
12
+ "@preact/signals": "^2.8.1",
13
+ "axios": "^1.13.6",
14
+ "preact": "^10.27.2",
15
+ "preact-iso": "^2.11.1",
16
+ "preact-render-to-string": "^6.6.6",
17
+ "sonner": "^2.0.7"
18
+ },
19
+ "devDependencies": {
20
+ "@preact/preset-vite": "^2.10.2",
21
+ "@tailwindcss/vite": "^4.2.1",
22
+ "tailwindcss": "^4.2.1",
23
+ "vite": "^7.3.1"
24
+ }
25
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 362.05 512" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><g data-name="Layer 2"><path d="M360.58 214.71a15 15 0 0 0-13.58-8.53H203.51l77.06-185.41A15 15 0 0 0 266.71 0H103.37A15 15 0 0 0 89 10.71L.63 306.6A15 15 0 0 0 15 325.9h138.6l-43.34 167.33a15 15 0 0 0 26.22 13.18l222.25-275.8a15 15 0 0 0 1.85-15.9z" data-name="lighting bolt" fill="#22c55e" opacity="1" data-original="#000000" class=""></path></g></g></svg>
Binary file
@@ -0,0 +1,22 @@
1
+ import Navigations from './navigations';
2
+ import { Toaster } from 'sonner';
3
+
4
+ const App = () => {
5
+ return (
6
+ <>
7
+ <Navigations />
8
+ <Toaster
9
+ richColors
10
+ position="top-right"
11
+ closeButton
12
+ toastOptions={{
13
+ style: {
14
+ fontFamily: 'var(--font-sans)',
15
+ },
16
+ }}
17
+ />
18
+ </>
19
+ );
20
+ };
21
+
22
+ export default App;
@@ -0,0 +1,50 @@
1
+ const ConfirmModal = ({
2
+ open,
3
+ title,
4
+ description,
5
+ confirmText = 'Confirm',
6
+ cancelText = 'Cancel',
7
+ confirmVariant = 'danger',
8
+ loading = false,
9
+ onConfirm,
10
+ onCancel,
11
+ }) => {
12
+ if (!open) return null;
13
+
14
+ return (
15
+ <div class="fixed inset-0 z-50 flex items-center justify-center px-4">
16
+ <button
17
+ type="button"
18
+ class="absolute inset-0 bg-black/70 backdrop-blur-sm"
19
+ aria-label="Close confirm dialog"
20
+ onClick={onCancel}
21
+ />
22
+
23
+ <div class="relative z-10 w-full max-w-md p-5 sm:p-6 bg-zinc-900 border border-zinc-800">
24
+ <p class="text-base font-semibold text-zinc-100">{title}</p>
25
+ <p class="mt-2 text-sm text-zinc-400">{description}</p>
26
+
27
+ <div class="mt-5 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
28
+ <button
29
+ type="button"
30
+ class="inline-flex items-center gap-3 px-4 py-2 text-sm font-semibold uppercase tracking-wide border border-zinc-700 text-zinc-300 hover:border-zinc-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed justify-center sm:w-auto"
31
+ disabled={loading}
32
+ onClick={onCancel}
33
+ >
34
+ {cancelText}
35
+ </button>
36
+ <button
37
+ type="button"
38
+ class={`inline-flex items-center gap-3 px-4 py-2 text-sm font-semibold uppercase tracking-wide transition-colors disabled:opacity-50 disabled:cursor-not-allowed justify-center sm:w-auto ${confirmVariant === 'danger' ? 'border border-red-800 text-red-400 hover:bg-red-950' : 'border border-zinc-700 text-zinc-300 hover:border-zinc-500'}`}
39
+ disabled={loading}
40
+ onClick={onConfirm}
41
+ >
42
+ {loading ? 'Processing...' : confirmText}
43
+ </button>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ );
48
+ };
49
+
50
+ export default ConfirmModal;
@@ -0,0 +1,377 @@
1
+ export const IconGoogle = (props) => (
2
+ <svg viewBox="0 0 24 24" {...props}>
3
+ <path
4
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
5
+ fill="#4285F4"
6
+ />
7
+ <path
8
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
9
+ fill="#34A853"
10
+ />
11
+ <path
12
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l3.66-2.84z"
13
+ fill="#FBBC05"
14
+ />
15
+ <path
16
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
17
+ fill="#EA4335"
18
+ />
19
+ </svg>
20
+ );
21
+
22
+ export const IconDashboard = (props) => (
23
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
24
+ <rect x="3" y="3" width="8" height="8" rx="2" fill="currentColor" />
25
+ <rect
26
+ x="13"
27
+ y="3"
28
+ width="8"
29
+ height="5"
30
+ rx="2"
31
+ fill="currentColor"
32
+ opacity="0.8"
33
+ />
34
+ <rect x="13" y="10" width="8" height="11" rx="2" fill="currentColor" />
35
+ <rect
36
+ x="3"
37
+ y="13"
38
+ width="8"
39
+ height="8"
40
+ rx="2"
41
+ fill="currentColor"
42
+ opacity="0.8"
43
+ />
44
+ </svg>
45
+ );
46
+
47
+ export const IconKey = (props) => (
48
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
49
+ <circle cx="8" cy="12" r="4" stroke="currentColor" strokeWidth="2" />
50
+ <path
51
+ d="M12 12h9m-3 0v3m-3-3v2"
52
+ stroke="currentColor"
53
+ strokeWidth="2"
54
+ strokeLinecap="round"
55
+ />
56
+ </svg>
57
+ );
58
+
59
+ export const IconWallet = (props) => (
60
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
61
+ <rect
62
+ x="3"
63
+ y="6"
64
+ width="18"
65
+ height="12"
66
+ rx="3"
67
+ stroke="currentColor"
68
+ strokeWidth="2"
69
+ />
70
+ <path d="M16 11h5v4h-5a2 2 0 0 1 0-4z" fill="currentColor" opacity="0.85" />
71
+ </svg>
72
+ );
73
+
74
+ export const IconDocs = (props) => (
75
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
76
+ <path
77
+ d="M7 4h8l4 4v12H7a3 3 0 1 0 0-6h12"
78
+ stroke="currentColor"
79
+ strokeWidth="2"
80
+ strokeLinejoin="round"
81
+ />
82
+ <path d="M15 4v4h4" stroke="currentColor" strokeWidth="2" />
83
+ </svg>
84
+ );
85
+
86
+ export const IconLogout = (props) => (
87
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
88
+ <path
89
+ d="M9 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h3"
90
+ stroke="currentColor"
91
+ strokeWidth="2"
92
+ strokeLinecap="round"
93
+ />
94
+ <path
95
+ d="M13 16l4-4-4-4"
96
+ stroke="currentColor"
97
+ strokeWidth="2"
98
+ strokeLinecap="round"
99
+ strokeLinejoin="round"
100
+ />
101
+ <path
102
+ d="M17 12H9"
103
+ stroke="currentColor"
104
+ strokeWidth="2"
105
+ strokeLinecap="round"
106
+ />
107
+ </svg>
108
+ );
109
+
110
+ export const IconMenu = (props) => (
111
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
112
+ <path
113
+ d="M4 7h16M4 12h16M4 17h16"
114
+ stroke="currentColor"
115
+ strokeWidth="2"
116
+ strokeLinecap="round"
117
+ />
118
+ </svg>
119
+ );
120
+
121
+ export const IconCopy = (props) => (
122
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
123
+ <rect
124
+ x="9"
125
+ y="9"
126
+ width="11"
127
+ height="11"
128
+ rx="2"
129
+ stroke="currentColor"
130
+ strokeWidth="2"
131
+ />
132
+ <path
133
+ d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"
134
+ stroke="currentColor"
135
+ strokeWidth="2"
136
+ strokeLinecap="round"
137
+ />
138
+ </svg>
139
+ );
140
+
141
+ export const IconEdit = (props) => (
142
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
143
+ <path
144
+ d="M4 20h4l10-10-4-4L4 16v4z"
145
+ stroke="currentColor"
146
+ strokeWidth="2"
147
+ strokeLinejoin="round"
148
+ />
149
+ <path
150
+ d="M12.5 7.5l4 4"
151
+ stroke="currentColor"
152
+ strokeWidth="2"
153
+ strokeLinecap="round"
154
+ />
155
+ </svg>
156
+ );
157
+
158
+ export const IconTrash = (props) => (
159
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
160
+ <path
161
+ d="M4 7h16"
162
+ stroke="currentColor"
163
+ strokeWidth="2"
164
+ strokeLinecap="round"
165
+ />
166
+ <path
167
+ d="M10 11v6M14 11v6"
168
+ stroke="currentColor"
169
+ strokeWidth="2"
170
+ strokeLinecap="round"
171
+ />
172
+ <path
173
+ d="M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12"
174
+ stroke="currentColor"
175
+ strokeWidth="2"
176
+ strokeLinejoin="round"
177
+ />
178
+ <path
179
+ d="M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"
180
+ stroke="currentColor"
181
+ strokeWidth="2"
182
+ />
183
+ </svg>
184
+ );
185
+
186
+ export const IconRefresh = (props) => (
187
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
188
+ <path
189
+ d="M20 7v5h-5"
190
+ stroke="currentColor"
191
+ strokeWidth="2"
192
+ strokeLinecap="round"
193
+ strokeLinejoin="round"
194
+ />
195
+ <path
196
+ d="M4 17v-5h5"
197
+ stroke="currentColor"
198
+ strokeWidth="2"
199
+ strokeLinecap="round"
200
+ strokeLinejoin="round"
201
+ />
202
+ <path
203
+ d="M20 12a8 8 0 0 0-14-5M4 12a8 8 0 0 0 14 5"
204
+ stroke="currentColor"
205
+ strokeWidth="2"
206
+ strokeLinecap="round"
207
+ />
208
+ </svg>
209
+ );
210
+
211
+ export const IconCheck = (props) => (
212
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
213
+ <path
214
+ d="M5 13l4 4L19 7"
215
+ stroke="currentColor"
216
+ strokeWidth="2"
217
+ strokeLinecap="round"
218
+ strokeLinejoin="round"
219
+ />
220
+ </svg>
221
+ );
222
+
223
+ export const IconLogo = ({ fill = '#22c55e', className, ...props }) => (
224
+ <svg viewBox="0 0 362.05 512" className={className} {...props}>
225
+ <g>
226
+ <g data-name="Layer 2">
227
+ <path
228
+ d="M360.58 214.71a15 15 0 0 0-13.58-8.53H203.51l77.06-185.41A15 15 0 0 0 266.71 0H103.37A15 15 0 0 0 89 10.71L.63 306.6A15 15 0 0 0 15 325.9h138.6l-43.34 167.33a15 15 0 0 0 26.22 13.18l222.25-275.8a15 15 0 0 0 1.85-15.9z"
229
+ data-name="lighting bolt"
230
+ fill={fill}
231
+ ></path>
232
+ </g>
233
+ </g>
234
+ </svg>
235
+ );
236
+
237
+ export const IconTelegram = (props) => (
238
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
239
+ <path
240
+ d="M21.5 4.5L18.8 19.6c-.2 1.1-.8 1.4-1.7.9l-4.8-3.5-2.3 2.2c-.2.3-.5.5-1 .5l.4-4.9 8.8-8c.4-.3-.1-.5-.5-.2L6.7 13 2 11.5c-1.1-.3-1.1-1.1.2-1.6L20.8 3c1-.4 1.8.2 1.7 1.5z"
241
+ fill="currentColor"
242
+ />
243
+ </svg>
244
+ );
245
+
246
+ export const IconChevronLeft = (props) => (
247
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
248
+ <path
249
+ d="M15 6l-6 6 6 6"
250
+ stroke="currentColor"
251
+ strokeWidth="2"
252
+ strokeLinecap="round"
253
+ strokeLinejoin="round"
254
+ />
255
+ </svg>
256
+ );
257
+
258
+ export const IconChevronRight = (props) => (
259
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
260
+ <path
261
+ d="M9 6l6 6-6 6"
262
+ stroke="currentColor"
263
+ strokeWidth="2"
264
+ strokeLinecap="round"
265
+ strokeLinejoin="round"
266
+ />
267
+ </svg>
268
+ );
269
+
270
+ export const IconBank = (props) => (
271
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
272
+ <path
273
+ d="M3 9l9-5 9 5"
274
+ stroke="currentColor"
275
+ strokeWidth="2"
276
+ strokeLinecap="round"
277
+ strokeLinejoin="round"
278
+ />
279
+ <path
280
+ d="M5 10v8M9 10v8M15 10v8M19 10v8"
281
+ stroke="currentColor"
282
+ strokeWidth="2"
283
+ strokeLinecap="round"
284
+ />
285
+ <path
286
+ d="M3 20h18"
287
+ stroke="currentColor"
288
+ strokeWidth="2"
289
+ strokeLinecap="round"
290
+ />
291
+ </svg>
292
+ );
293
+
294
+ export const IconCard = (props) => (
295
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
296
+ <rect
297
+ x="3"
298
+ y="5"
299
+ width="18"
300
+ height="14"
301
+ rx="3"
302
+ stroke="currentColor"
303
+ strokeWidth="2"
304
+ />
305
+ <path d="M3 10h18" stroke="currentColor" strokeWidth="2" />
306
+ <path
307
+ d="M7 15h4"
308
+ stroke="currentColor"
309
+ strokeWidth="2"
310
+ strokeLinecap="round"
311
+ />
312
+ </svg>
313
+ );
314
+
315
+ export const IconCoin = (props) => (
316
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
317
+ <circle cx="12" cy="12" r="8" stroke="currentColor" strokeWidth="2" />
318
+ <path
319
+ d="M12 8v8M9.5 10.5c0-1.1 1.1-2 2.5-2s2.5.9 2.5 2-1.1 2-2.5 2-2.5.9-2.5 2 1.1 2 2.5 2 2.5-.9 2.5-2"
320
+ stroke="currentColor"
321
+ strokeWidth="2"
322
+ strokeLinecap="round"
323
+ />
324
+ </svg>
325
+ );
326
+
327
+ export const IconClock = (props) => (
328
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
329
+ <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2" />
330
+ <path
331
+ d="M12 7v5l3 2"
332
+ stroke="currentColor"
333
+ strokeWidth="2"
334
+ strokeLinecap="round"
335
+ strokeLinejoin="round"
336
+ />
337
+ </svg>
338
+ );
339
+
340
+ export const IconInfo = (props) => (
341
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
342
+ <circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2" />
343
+ <path
344
+ d="M12 11v5"
345
+ stroke="currentColor"
346
+ strokeWidth="2"
347
+ strokeLinecap="round"
348
+ />
349
+ <circle cx="12" cy="8" r="1" fill="currentColor" />
350
+ </svg>
351
+ );
352
+
353
+ export const IconPlay = (props) => (
354
+ <svg viewBox="0 0 24 24" fill="none" {...props}>
355
+ <path
356
+ d="M6 4l14 8-14 8V4z"
357
+ fill="currentColor"
358
+ />
359
+ </svg>
360
+ );
361
+
362
+ export const IconEye = ({ off, ...props }) => (
363
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...props}>
364
+ {off ? (
365
+ <>
366
+ <path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94" />
367
+ <path d="M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19" />
368
+ <line x1="1" y1="1" x2="23" y2="23" />
369
+ </>
370
+ ) : (
371
+ <>
372
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
373
+ <circle cx="12" cy="12" r="3" />
374
+ </>
375
+ )}
376
+ </svg>
377
+ );
@@ -0,0 +1,14 @@
1
+ import { useEffect } from 'preact/hooks';
2
+ import { useLocation } from 'preact-iso';
3
+
4
+ const RedirectRoute = ({ to }) => {
5
+ const { route } = useLocation();
6
+
7
+ useEffect(() => {
8
+ route(to);
9
+ }, [route, to]);
10
+
11
+ return null;
12
+ };
13
+
14
+ export default RedirectRoute;
@@ -0,0 +1,15 @@
1
+ const SplashScreen = () => (
2
+ <div class="ui-page px-6 py-12">
3
+ <div class="ui-card flex items-center gap-4 p-8">
4
+ <div class="h-10 w-10 rounded-full border-4 border-[var(--color-border)] border-t-[var(--color-primary)] animate-spin" />
5
+ <div>
6
+ <p class="text-sm font-semibold text-[var(--color-text)]">
7
+ crewOS
8
+ </p>
9
+ <p class="text-xs text-[var(--color-muted)]">Loading workspace...</p>
10
+ </div>
11
+ </div>
12
+ </div>
13
+ );
14
+
15
+ export default SplashScreen;
@@ -0,0 +1,28 @@
1
+ import { useEffect } from 'preact/hooks';
2
+
3
+ import { auth as authStore } from '../stores/auth.store';
4
+ import { getMe } from '../services/auth.service';
5
+
6
+ export const useAuth = () => {
7
+ useEffect(() => {
8
+ const check = async () => {
9
+ try {
10
+ const res = await getMe();
11
+ authStore.value = {
12
+ user: { authenticated: true },
13
+ initialized: true,
14
+ onboardingCompleted: res.data?.onboardingCompleted || false,
15
+ };
16
+ } catch {
17
+ localStorage.removeItem('crewos_token');
18
+ authStore.value = {
19
+ user: null,
20
+ initialized: true,
21
+ onboardingCompleted: false,
22
+ };
23
+ }
24
+ };
25
+
26
+ check();
27
+ }, []);
28
+ };