@onahhas/hello-dev 1.0.0 → 1.0.1

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 (70) hide show
  1. package/README.md +149 -11
  2. package/backend/Controllers/AccountController.cs +100 -0
  3. package/backend/Controllers/ActivityController.cs +44 -0
  4. package/backend/Controllers/AuthController.cs +127 -0
  5. package/backend/Controllers/LookupController.cs +46 -0
  6. package/backend/Controllers/TasksController.cs +652 -0
  7. package/backend/Controllers/UsersController.cs +181 -0
  8. package/backend/Data/AppDbContext.cs +93 -0
  9. package/backend/Data/DbSeeder.cs +122 -0
  10. package/backend/DevTasks.Api.csproj +13 -0
  11. package/backend/Dtos/ActivityDtos.cs +12 -0
  12. package/backend/Dtos/AuthDtos.cs +37 -0
  13. package/backend/Dtos/TaskDtos.cs +104 -0
  14. package/backend/Dtos/UserDtos.cs +29 -0
  15. package/backend/Enums/EditRequestStatus.cs +8 -0
  16. package/backend/Enums/TaskPriority.cs +8 -0
  17. package/backend/Enums/TaskState.cs +9 -0
  18. package/backend/Enums/TaskVisibility.cs +7 -0
  19. package/backend/Enums/UserRole.cs +7 -0
  20. package/backend/Extensions/ClaimsPrincipalExtensions.cs +23 -0
  21. package/backend/Models/ActivityLog.cs +12 -0
  22. package/backend/Models/AppUser.cs +17 -0
  23. package/backend/Models/TaskEditRequest.cs +31 -0
  24. package/backend/Models/TaskItem.cs +25 -0
  25. package/backend/Program.cs +138 -0
  26. package/backend/Properties/launchSettings.json +13 -0
  27. package/backend/Services/ActivityService.cs +28 -0
  28. package/backend/Services/PasswordHasher.cs +58 -0
  29. package/backend/Services/TokenService.cs +49 -0
  30. package/backend/appsettings.Development.json +10 -0
  31. package/backend/appsettings.json +24 -0
  32. package/frontend/index.html +12 -0
  33. package/frontend/package-lock.json +1769 -0
  34. package/frontend/package.json +23 -0
  35. package/frontend/src/App.tsx +40 -0
  36. package/frontend/src/api/http.ts +75 -0
  37. package/frontend/src/auth/AuthContext.tsx +101 -0
  38. package/frontend/src/components/EditRequestModal.tsx +139 -0
  39. package/frontend/src/components/EditRequestsPanel.tsx +94 -0
  40. package/frontend/src/components/Layout.tsx +76 -0
  41. package/frontend/src/components/PageHeader.tsx +21 -0
  42. package/frontend/src/components/ProtectedRoute.tsx +14 -0
  43. package/frontend/src/components/StatCard.tsx +15 -0
  44. package/frontend/src/components/TaskCard.tsx +83 -0
  45. package/frontend/src/components/TaskDetailsModal.tsx +45 -0
  46. package/frontend/src/components/TaskFilters.tsx +67 -0
  47. package/frontend/src/components/TaskModal.tsx +159 -0
  48. package/frontend/src/components/TaskTable.tsx +68 -0
  49. package/frontend/src/components/UserModal.tsx +124 -0
  50. package/frontend/src/main.tsx +19 -0
  51. package/frontend/src/pages/ActivityPage.tsx +37 -0
  52. package/frontend/src/pages/BoardPage.tsx +75 -0
  53. package/frontend/src/pages/CalendarPage.tsx +101 -0
  54. package/frontend/src/pages/DashboardPage.tsx +131 -0
  55. package/frontend/src/pages/LoginPage.tsx +69 -0
  56. package/frontend/src/pages/ProfilePage.tsx +111 -0
  57. package/frontend/src/pages/PublicTasksPage.tsx +99 -0
  58. package/frontend/src/pages/RegisterPage.tsx +80 -0
  59. package/frontend/src/pages/TasksPage.tsx +135 -0
  60. package/frontend/src/pages/UsersPage.tsx +86 -0
  61. package/frontend/src/styles.css +596 -0
  62. package/frontend/src/theme.tsx +49 -0
  63. package/frontend/src/types.ts +78 -0
  64. package/frontend/src/utils/date.ts +30 -0
  65. package/frontend/src/utils/labels.ts +3 -0
  66. package/frontend/src/vite-env.d.ts +1 -0
  67. package/frontend/tsconfig.json +21 -0
  68. package/frontend/vite.config.ts +15 -0
  69. package/package.json +22 -9
  70. package/index.js +0 -7
@@ -0,0 +1,596 @@
1
+ :root {
2
+ --bg: #f7f1e8;
3
+ --bg2: #efe3d2;
4
+ --panel: rgba(255, 252, 246, 0.92);
5
+ --panel-solid: #fffaf1;
6
+ --panel-strong: #f0dfc5;
7
+ --ink: #201c1f;
8
+ --muted: #766d68;
9
+ --border: #dfcfb8;
10
+ --brand: #7a3d54;
11
+ --brand-strong: #4e2435;
12
+ --accent: #c1703a;
13
+ --accent-soft: #f4d9c7;
14
+ --success: #5c7a58;
15
+ --success-soft: #dcead9;
16
+ --warning: #aa7a28;
17
+ --danger: #a4473d;
18
+ --danger-soft: #f3d1ca;
19
+ --shadow: 0 24px 70px rgba(67, 42, 29, 0.14);
20
+ --radius: 24px;
21
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
22
+ color: var(--ink);
23
+ background: var(--bg);
24
+ }
25
+
26
+ :root[data-theme="dark"] {
27
+ --bg: #121111;
28
+ --bg2: #1d171b;
29
+ --panel: rgba(34, 30, 32, 0.92);
30
+ --panel-solid: #211d20;
31
+ --panel-strong: #30282d;
32
+ --ink: #fbf2e8;
33
+ --muted: #b9aaa0;
34
+ --border: #473d3d;
35
+ --brand: #d591ab;
36
+ --brand-strong: #f3bed2;
37
+ --accent: #e0985b;
38
+ --accent-soft: rgba(224, 152, 91, 0.16);
39
+ --success: #9cc497;
40
+ --success-soft: rgba(156, 196, 151, 0.14);
41
+ --warning: #e1b768;
42
+ --danger: #ee9085;
43
+ --danger-soft: rgba(238, 144, 133, 0.15);
44
+ --shadow: 0 26px 80px rgba(0, 0, 0, 0.35);
45
+ }
46
+
47
+ * { box-sizing: border-box; }
48
+
49
+ html,
50
+ body,
51
+ #root {
52
+ min-height: 100%;
53
+ margin: 0;
54
+ }
55
+
56
+ body {
57
+ background:
58
+ radial-gradient(circle at 18% -10%, rgba(122, 61, 84, 0.24), transparent 35rem),
59
+ radial-gradient(circle at 94% 5%, rgba(193, 112, 58, 0.18), transparent 30rem),
60
+ linear-gradient(135deg, var(--bg), var(--bg2));
61
+ }
62
+
63
+ button,
64
+ input,
65
+ select,
66
+ textarea { font: inherit; }
67
+ button { cursor: pointer; }
68
+ button:disabled { cursor: not-allowed; opacity: 0.62; }
69
+ a { color: var(--brand-strong); text-decoration: none; font-weight: 800; }
70
+ a:hover { text-decoration: underline; }
71
+
72
+ label {
73
+ display: grid;
74
+ gap: 0.48rem;
75
+ color: var(--muted);
76
+ font-size: 0.9rem;
77
+ font-weight: 800;
78
+ }
79
+
80
+ input,
81
+ select,
82
+ textarea {
83
+ width: 100%;
84
+ border: 1px solid var(--border);
85
+ border-radius: 16px;
86
+ background: color-mix(in srgb, var(--panel-solid) 92%, transparent);
87
+ color: var(--ink);
88
+ padding: 0.86rem 0.95rem;
89
+ outline: none;
90
+ transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
91
+ }
92
+
93
+ input:focus,
94
+ select:focus,
95
+ textarea:focus {
96
+ border-color: var(--brand);
97
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--brand) 16%, transparent);
98
+ }
99
+
100
+ textarea { resize: none; }
101
+ small { color: var(--muted); }
102
+
103
+ .authPage {
104
+ min-height: 100vh;
105
+ display: grid;
106
+ grid-template-columns: minmax(0, 1fr) 430px;
107
+ gap: 2rem;
108
+ align-items: center;
109
+ padding: 3rem;
110
+ }
111
+
112
+ .authVisual {
113
+ min-height: 74vh;
114
+ border-radius: 38px;
115
+ padding: 3.5rem;
116
+ display: flex;
117
+ flex-direction: column;
118
+ justify-content: flex-end;
119
+ position: relative;
120
+ overflow: hidden;
121
+ color: #fff8ef;
122
+ background:
123
+ linear-gradient(135deg, rgba(29, 22, 26, 0.94), rgba(122, 61, 84, 0.76)),
124
+ repeating-linear-gradient(135deg, rgba(255,255,255,0.08) 0 1px, transparent 1px 18px);
125
+ box-shadow: var(--shadow);
126
+ }
127
+
128
+ .authVisual::before {
129
+ content: "";
130
+ position: absolute;
131
+ inset: auto -8rem -12rem auto;
132
+ width: 28rem;
133
+ height: 28rem;
134
+ border-radius: 50%;
135
+ background: rgba(193, 112, 58, 0.38);
136
+ filter: blur(4px);
137
+ }
138
+
139
+ .authVisual h1 {
140
+ position: relative;
141
+ max-width: 760px;
142
+ font-size: clamp(2.5rem, 6vw, 6rem);
143
+ line-height: 0.92;
144
+ letter-spacing: -0.08em;
145
+ margin: 1.2rem 0;
146
+ }
147
+
148
+ .authVisual p {
149
+ position: relative;
150
+ max-width: 620px;
151
+ color: rgba(255, 248, 239, 0.76);
152
+ font-size: 1.1rem;
153
+ line-height: 1.7;
154
+ }
155
+
156
+ .authBadge,
157
+ .eyebrow {
158
+ width: fit-content;
159
+ text-transform: uppercase;
160
+ letter-spacing: 0.14em;
161
+ font-size: 0.72rem;
162
+ font-weight: 950;
163
+ }
164
+
165
+ .authBadge {
166
+ position: relative;
167
+ padding: 0.55rem 0.9rem;
168
+ border: 1px solid rgba(255, 248, 239, 0.28);
169
+ border-radius: 999px;
170
+ color: #ffe0c6;
171
+ background: rgba(255, 248, 239, 0.08);
172
+ }
173
+
174
+ .authThemeBtn {
175
+ position: absolute;
176
+ top: 1.3rem;
177
+ right: 1.3rem;
178
+ border: 1px solid rgba(255, 248, 239, 0.28);
179
+ background: rgba(255, 248, 239, 0.08);
180
+ color: #fff8ef;
181
+ border-radius: 999px;
182
+ padding: 0.7rem 1rem;
183
+ font-weight: 900;
184
+ }
185
+
186
+ .authCard,
187
+ .panelCard,
188
+ .tableCard,
189
+ .timelineCard,
190
+ .calendarCard,
191
+ .profileCardLarge,
192
+ .modalCard,
193
+ .heroBoard {
194
+ border: 1px solid var(--border);
195
+ border-radius: var(--radius);
196
+ background: var(--panel);
197
+ box-shadow: var(--shadow);
198
+ backdrop-filter: blur(18px);
199
+ }
200
+
201
+ .authCard {
202
+ display: grid;
203
+ gap: 1.1rem;
204
+ padding: 2rem;
205
+ }
206
+
207
+ .authCard h2,
208
+ .pageHeader h1,
209
+ .sectionTitle h2,
210
+ .modalHeader h2,
211
+ .heroBoard h2 {
212
+ margin: 0;
213
+ letter-spacing: -0.045em;
214
+ }
215
+
216
+ .authCard h2 { font-size: 2.35rem; }
217
+ .authCard p,
218
+ .pageHeader p,
219
+ .modalHeader p,
220
+ .heroBoard p {
221
+ color: var(--muted);
222
+ margin: 0;
223
+ line-height: 1.6;
224
+ }
225
+
226
+ .appShell {
227
+ min-height: 100vh;
228
+ display: grid;
229
+ grid-template-columns: 288px minmax(0, 1fr);
230
+ }
231
+
232
+ .sidebar {
233
+ position: sticky;
234
+ top: 0;
235
+ height: 100vh;
236
+ padding: 1.15rem;
237
+ display: flex;
238
+ flex-direction: column;
239
+ gap: 1rem;
240
+ background: color-mix(in srgb, var(--panel-solid) 74%, transparent);
241
+ border-right: 1px solid var(--border);
242
+ backdrop-filter: blur(18px);
243
+ }
244
+
245
+ .brandBlock {
246
+ display: flex;
247
+ align-items: center;
248
+ gap: 0.85rem;
249
+ padding: 1rem;
250
+ border-radius: 24px;
251
+ background: linear-gradient(135deg, var(--ink), var(--brand-strong));
252
+ color: var(--panel-solid);
253
+ }
254
+
255
+ .brandBlock span,
256
+ .profileMini small { display: block; color: color-mix(in srgb, var(--panel-solid) 68%, transparent); font-size: 0.78rem; margin-top: 0.12rem; }
257
+
258
+ .brandMark,
259
+ .avatar,
260
+ .profileAvatarLarge {
261
+ display: grid;
262
+ place-items: center;
263
+ flex: 0 0 auto;
264
+ font-weight: 950;
265
+ background: var(--accent);
266
+ color: #fff8ef;
267
+ }
268
+
269
+ .brandMark { width: 46px; height: 46px; border-radius: 16px; }
270
+ .avatar { width: 42px; height: 42px; border-radius: 15px; }
271
+
272
+ .navList { display: grid; gap: 0.35rem; }
273
+ .navList a {
274
+ display: flex;
275
+ align-items: center;
276
+ gap: 0.75rem;
277
+ padding: 0.86rem 0.92rem;
278
+ border-radius: 17px;
279
+ color: var(--muted);
280
+ font-weight: 900;
281
+ text-decoration: none;
282
+ }
283
+ .navList a span { width: 1.1rem; text-align: center; }
284
+ .navList a:hover,
285
+ .navList a.active {
286
+ background: var(--panel-strong);
287
+ color: var(--ink);
288
+ }
289
+
290
+ .themeSwitch {
291
+ margin-top: auto;
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: space-between;
295
+ gap: 0.7rem;
296
+ padding: 0.8rem;
297
+ border: 1px solid var(--border);
298
+ border-radius: 18px;
299
+ color: var(--muted);
300
+ font-size: 0.86rem;
301
+ font-weight: 900;
302
+ }
303
+
304
+ .sideFooter { display: grid; gap: 0.8rem; }
305
+ .profileMini {
306
+ display: flex;
307
+ align-items: center;
308
+ gap: 0.8rem;
309
+ padding: 0.85rem;
310
+ border-radius: 18px;
311
+ background: color-mix(in srgb, var(--ink) 5%, transparent);
312
+ color: var(--ink);
313
+ }
314
+
315
+ .mainPanel { padding: 2.2rem; overflow-x: hidden; }
316
+
317
+ .pageHeader {
318
+ display: flex;
319
+ justify-content: space-between;
320
+ gap: 1rem;
321
+ align-items: flex-start;
322
+ margin-bottom: 1.2rem;
323
+ }
324
+ .pageHeader h1 { font-size: clamp(2rem, 4vw, 3.7rem); }
325
+ .eyebrow { color: var(--accent); }
326
+ .headerActions,
327
+ .buttonGroup,
328
+ .modalActions,
329
+ .cardActions,
330
+ .rowActions { display: flex; flex-wrap: wrap; gap: 0.55rem; align-items: center; }
331
+
332
+ .primaryBtn,
333
+ .ghostBtn,
334
+ .dangerBtn,
335
+ .iconBtn {
336
+ border: 0;
337
+ border-radius: 999px;
338
+ padding: 0.78rem 1rem;
339
+ font-weight: 950;
340
+ text-decoration: none;
341
+ display: inline-flex;
342
+ align-items: center;
343
+ justify-content: center;
344
+ gap: 0.4rem;
345
+ }
346
+ .primaryBtn { background: var(--brand); color: #fff8ef; }
347
+ .ghostBtn { background: color-mix(in srgb, var(--ink) 7%, transparent); color: var(--ink); }
348
+ .dangerBtn { background: var(--danger-soft); color: var(--danger); }
349
+ .iconBtn { width: 42px; height: 42px; padding: 0; background: color-mix(in srgb, var(--ink) 7%, transparent); color: var(--ink); font-size: 1.35rem; }
350
+ .full { width: 100%; }
351
+
352
+ .heroBoard {
353
+ display: flex;
354
+ align-items: center;
355
+ justify-content: space-between;
356
+ gap: 1rem;
357
+ padding: 1.3rem;
358
+ margin-bottom: 1rem;
359
+ background:
360
+ linear-gradient(135deg, color-mix(in srgb, var(--brand) 13%, transparent), transparent),
361
+ var(--panel);
362
+ }
363
+ .heroBoard h2 { font-size: 2rem; }
364
+
365
+ .statsGrid {
366
+ display: grid;
367
+ grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
368
+ gap: 0.9rem;
369
+ margin: 1rem 0;
370
+ }
371
+ .statCard {
372
+ min-height: 136px;
373
+ padding: 1.1rem;
374
+ border: 1px solid var(--border);
375
+ border-radius: 24px;
376
+ background: var(--panel);
377
+ box-shadow: 0 16px 40px rgba(38, 25, 16, 0.08);
378
+ }
379
+ .statCard span { color: var(--muted); font-weight: 900; }
380
+ .statCard strong { display: block; font-size: 2.3rem; letter-spacing: -0.06em; margin: 0.45rem 0 0.1rem; }
381
+ .statCard small { display: block; }
382
+
383
+ .twoColumns {
384
+ display: grid;
385
+ grid-template-columns: minmax(0, 1.15fr) minmax(320px, 0.85fr);
386
+ gap: 1rem;
387
+ margin-top: 1rem;
388
+ }
389
+ .panelCard { padding: 1.1rem; }
390
+ .sectionTitle {
391
+ display: flex;
392
+ align-items: center;
393
+ justify-content: space-between;
394
+ gap: 1rem;
395
+ margin-bottom: 0.9rem;
396
+ }
397
+
398
+ .filtersBar {
399
+ display: grid;
400
+ grid-template-columns: minmax(240px, 1fr) repeat(3, minmax(150px, 190px));
401
+ gap: 0.75rem;
402
+ margin: 1rem 0;
403
+ }
404
+
405
+ .taskList {
406
+ display: grid;
407
+ grid-template-columns: repeat(auto-fill, minmax(290px, 1fr));
408
+ gap: 0.9rem;
409
+ }
410
+ .compactList { grid-template-columns: 1fr; }
411
+ .taskCard {
412
+ position: relative;
413
+ overflow: hidden;
414
+ display: grid;
415
+ gap: 0.75rem;
416
+ padding: 1rem;
417
+ border: 1px solid var(--border);
418
+ border-radius: 24px;
419
+ background: var(--panel);
420
+ box-shadow: 0 16px 42px rgba(38, 25, 16, 0.08);
421
+ }
422
+ .taskCard::before {
423
+ content: "";
424
+ position: absolute;
425
+ inset: 0 auto 0 0;
426
+ width: 5px;
427
+ background: var(--brand);
428
+ }
429
+ .taskCard.priorityHigh::before { background: var(--danger); }
430
+ .taskCard.priorityMedium::before { background: var(--warning); }
431
+ .taskCard.priorityLow::before { background: var(--success); }
432
+ .taskCard.overdue { outline: 2px solid color-mix(in srgb, var(--danger) 26%, transparent); }
433
+ .taskTopLine { display: flex; align-items: center; gap: 0.55rem; flex-wrap: wrap; }
434
+ .taskNumber,
435
+ .monoCell { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-weight: 950; color: var(--brand-strong); }
436
+ .taskCard h3 { margin: 0; font-size: 1.15rem; letter-spacing: -0.025em; }
437
+ .taskCard p { margin: 0; color: var(--muted); line-height: 1.55; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
438
+ .taskMeta { display: flex; flex-wrap: wrap; gap: 0.45rem; color: var(--muted); font-size: 0.78rem; }
439
+ .taskMeta span,
440
+ .tinyText,
441
+ .softBadge,
442
+ .countBadge,
443
+ .priorityBadge,
444
+ .dangerBadge {
445
+ border-radius: 999px;
446
+ padding: 0.32rem 0.55rem;
447
+ background: color-mix(in srgb, var(--ink) 7%, transparent);
448
+ color: var(--muted);
449
+ font-size: 0.78rem;
450
+ font-weight: 900;
451
+ }
452
+ .requestHint { color: var(--warning); font-size: 0.82rem; font-weight: 900; }
453
+
454
+ .statusPill {
455
+ border-radius: 999px;
456
+ padding: 0.32rem 0.58rem;
457
+ font-size: 0.76rem;
458
+ font-weight: 950;
459
+ background: color-mix(in srgb, var(--ink) 7%, transparent);
460
+ }
461
+ .statusPill.Todo { color: var(--muted); }
462
+ .statusPill.InProgress { color: var(--warning); }
463
+ .statusPill.Done { color: var(--success); }
464
+ .statusPill.Cancelled { color: var(--danger); }
465
+ .priorityBadge.High { color: var(--danger); background: var(--danger-soft); }
466
+ .priorityBadge.Medium { color: var(--warning); }
467
+ .priorityBadge.Low { color: var(--success); background: var(--success-soft); }
468
+ .dangerBadge { color: var(--danger); background: var(--danger-soft); }
469
+
470
+ .tableCard { overflow-x: auto; }
471
+ .tableCard table { width: 100%; border-collapse: collapse; min-width: 950px; }
472
+ th, td { text-align: left; padding: 0.85rem; border-bottom: 1px solid var(--border); vertical-align: top; }
473
+ th { color: var(--muted); font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.08em; }
474
+ td small { display: block; max-width: 360px; margin-top: 0.24rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
475
+ tr:hover td { background: color-mix(in srgb, var(--ink) 3%, transparent); }
476
+
477
+ .requestPanel { margin: 1rem 0; }
478
+ .requestList { display: grid; gap: 0.8rem; }
479
+ .requestItem {
480
+ border: 1px solid var(--border);
481
+ border-radius: 20px;
482
+ padding: 0.9rem;
483
+ background: color-mix(in srgb, var(--panel-solid) 85%, transparent);
484
+ display: grid;
485
+ gap: 0.7rem;
486
+ }
487
+ .requestItem span { color: var(--muted); display: block; margin-top: 0.16rem; }
488
+ .changeGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 0.5rem; }
489
+ .changeGrid span { padding: 0.6rem; background: color-mix(in srgb, var(--ink) 5%, transparent); border-radius: 14px; margin: 0; }
490
+ .requestDescription { margin: 0; color: var(--muted); max-height: 7rem; overflow: auto; }
491
+
492
+ .boardGrid { display: grid; grid-template-columns: repeat(4, minmax(250px, 1fr)); gap: 0.9rem; align-items: start; overflow-x: auto; }
493
+ .boardColumn { min-height: 460px; border: 1px solid var(--border); border-radius: 24px; background: color-mix(in srgb, var(--panel-solid) 64%, transparent); padding: 0.8rem; }
494
+ .boardHeader { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; }
495
+ .boardHeader span { background: var(--panel-strong); border-radius: 999px; padding: 0.25rem 0.6rem; font-weight: 900; }
496
+ .boardTasks { display: grid; gap: 0.75rem; }
497
+ .emptyColumn,
498
+ .emptyText,
499
+ .emptyState { color: var(--muted); }
500
+ .emptyState { padding: 1.2rem; border: 1px dashed var(--border); border-radius: 20px; background: color-mix(in srgb, var(--panel-solid) 58%, transparent); }
501
+
502
+ .calendarCard { padding: 1rem; }
503
+ .calendarTitle { font-size: 1.5rem; font-weight: 950; margin-bottom: 1rem; }
504
+ .calendarWeekdays,
505
+ .calendarGrid { display: grid; grid-template-columns: repeat(7, minmax(120px, 1fr)); gap: 0.55rem; }
506
+ .calendarWeekdays span { color: var(--muted); font-size: 0.78rem; font-weight: 950; text-transform: uppercase; letter-spacing: 0.08em; }
507
+ .calendarCell { min-height: 130px; border: 1px solid var(--border); border-radius: 18px; padding: 0.65rem; background: color-mix(in srgb, var(--panel-solid) 78%, transparent); display: grid; align-content: start; gap: 0.35rem; }
508
+ .mutedDay { opacity: 0.55; }
509
+ .calendarTask { border: 0; border-radius: 12px; padding: 0.35rem 0.45rem; text-align: left; font-size: 0.78rem; font-weight: 900; background: var(--accent-soft); color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
510
+ .calendarTask.High { background: var(--danger-soft); }
511
+ .calendarTask.Low { background: var(--success-soft); }
512
+
513
+ .activityList,
514
+ .quickLinks { display: grid; gap: 0.75rem; }
515
+ .activityList article,
516
+ .quickLinks a,
517
+ .timelineItem {
518
+ border: 1px solid var(--border);
519
+ border-radius: 18px;
520
+ padding: 0.85rem;
521
+ background: color-mix(in srgb, var(--panel-solid) 72%, transparent);
522
+ }
523
+ .activityList span { display: block; color: var(--muted); margin-top: 0.2rem; }
524
+ .timelineCard { padding: 1rem; display: grid; gap: 0.8rem; }
525
+ .timelineItem { display: flex; gap: 0.8rem; }
526
+ .timelineDot { width: 12px; height: 12px; border-radius: 50%; background: var(--accent); margin-top: 0.3rem; flex: 0 0 auto; }
527
+ .timelineItem p { margin: 0.2rem 0; color: var(--muted); }
528
+
529
+ .profileCardLarge { padding: 1.2rem; display: flex; gap: 1rem; align-items: center; margin-bottom: 1rem; }
530
+ .profileAvatarLarge { width: 78px; height: 78px; border-radius: 26px; font-size: 2rem; }
531
+ .profileCardLarge h2 { margin: 0; }
532
+ .profileCardLarge p { margin: 0.2rem 0 0.7rem; color: var(--muted); }
533
+ .profileFields { display: flex; flex-wrap: wrap; gap: 0.6rem; }
534
+ .profileFields span { padding: 0.42rem 0.7rem; border-radius: 999px; background: color-mix(in srgb, var(--ink) 7%, transparent); }
535
+ .profileForm { max-width: 900px; }
536
+
537
+ .modalLayer {
538
+ position: fixed;
539
+ inset: 0;
540
+ z-index: 50;
541
+ display: grid;
542
+ place-items: center;
543
+ padding: 1.2rem;
544
+ background: rgba(12, 10, 10, 0.58);
545
+ }
546
+ .modalCard {
547
+ width: min(560px, 100%);
548
+ max-height: calc(100vh - 2.4rem);
549
+ padding: 1.2rem;
550
+ display: grid;
551
+ gap: 1rem;
552
+ }
553
+ .wideModal { width: min(880px, 100%); }
554
+ .modalHeader { display: flex; justify-content: space-between; gap: 1rem; align-items: flex-start; }
555
+ .modalBodyGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.9rem; }
556
+ .spanTwo { grid-column: 1 / -1; }
557
+ .descriptionField textarea { height: 180px; max-height: 180px; overflow: auto; }
558
+ .detailsModal { gap: 1rem; }
559
+ .detailPills { display: flex; flex-wrap: wrap; gap: 0.5rem; }
560
+ .descriptionBox { max-height: 240px; overflow: auto; white-space: pre-wrap; line-height: 1.6; color: var(--muted); background: color-mix(in srgb, var(--ink) 5%, transparent); padding: 1rem; border-radius: 18px; }
561
+ .detailsGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 0.7rem; }
562
+ .detailsGrid span { display: grid; gap: 0.15rem; color: var(--muted); padding: 0.75rem; border-radius: 16px; background: color-mix(in srgb, var(--ink) 5%, transparent); }
563
+ .detailsGrid strong { color: var(--ink); }
564
+
565
+ .formGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.9rem; }
566
+ .errorBox,
567
+ .successBox { border-radius: 16px; padding: 0.85rem 1rem; font-weight: 850; }
568
+ .errorBox { background: var(--danger-soft); color: var(--danger); }
569
+ .successBox { background: var(--success-soft); color: var(--success); }
570
+ .statusDot { border-radius: 999px; padding: 0.35rem 0.65rem; font-weight: 900; }
571
+ .statusDot.active { background: var(--success-soft); color: var(--success); }
572
+ .statusDot.inactive { background: var(--danger-soft); color: var(--danger); }
573
+
574
+ @media (max-width: 1100px) {
575
+ .authPage { grid-template-columns: 1fr; padding: 1.4rem; }
576
+ .authVisual { min-height: 420px; }
577
+ .appShell { grid-template-columns: 1fr; }
578
+ .sidebar { position: static; height: auto; }
579
+ .navList { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
580
+ .themeSwitch { margin-top: 0; }
581
+ .twoColumns { grid-template-columns: 1fr; }
582
+ .filtersBar { grid-template-columns: 1fr 1fr; }
583
+ .boardGrid { grid-template-columns: repeat(4, minmax(260px, 1fr)); }
584
+ }
585
+
586
+ @media (max-width: 720px) {
587
+ .mainPanel { padding: 1rem; }
588
+ .pageHeader { display: grid; }
589
+ .filtersBar,
590
+ .formGrid,
591
+ .modalBodyGrid { grid-template-columns: 1fr; }
592
+ .spanTwo { grid-column: auto; }
593
+ .calendarWeekdays,
594
+ .calendarGrid { grid-template-columns: repeat(7, minmax(82px, 1fr)); }
595
+ .calendarCell { min-height: 112px; }
596
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ type ReactNode
9
+ } from 'react';
10
+
11
+ type Theme = 'light' | 'dark';
12
+
13
+ interface ThemeContextValue {
14
+ theme: Theme;
15
+ toggleTheme: () => void;
16
+ }
17
+
18
+ const THEME_KEY = 'devtasks_theme';
19
+ const ThemeContext = createContext<ThemeContextValue | null>(null);
20
+
21
+ function readTheme(): Theme {
22
+ const stored = localStorage.getItem(THEME_KEY);
23
+ if (stored === 'light' || stored === 'dark') return stored;
24
+
25
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
26
+ }
27
+
28
+ export function ThemeProvider({ children }: { children: ReactNode }) {
29
+ const [theme, setTheme] = useState<Theme>(() => readTheme());
30
+
31
+ useEffect(() => {
32
+ document.documentElement.dataset.theme = theme;
33
+ localStorage.setItem(THEME_KEY, theme);
34
+ }, [theme]);
35
+
36
+ const toggleTheme = useCallback(() => {
37
+ setTheme((current) => current === 'dark' ? 'light' : 'dark');
38
+ }, []);
39
+
40
+ const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
41
+
42
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
43
+ }
44
+
45
+ export function useTheme() {
46
+ const value = useContext(ThemeContext);
47
+ if (!value) throw new Error('useTheme must be used inside ThemeProvider.');
48
+ return value;
49
+ }
@@ -0,0 +1,78 @@
1
+ export type UserRole = 'Admin' | 'User';
2
+ export type TaskStatus = 'Todo' | 'InProgress' | 'Done' | 'Cancelled';
3
+ export type TaskPriority = 'Low' | 'Medium' | 'High';
4
+ export type TaskVisibility = 'Private' | 'Public';
5
+ export type EditRequestStatus = 'Pending' | 'Accepted' | 'Rejected';
6
+
7
+ export interface User {
8
+ id: string;
9
+ fullName: string;
10
+ email: string;
11
+ role: UserRole;
12
+ isActive: boolean;
13
+ createdAt: string;
14
+ }
15
+
16
+ export interface TaskItem {
17
+ id: number;
18
+ title: string;
19
+ description: string;
20
+ status: TaskStatus;
21
+ priority: TaskPriority;
22
+ visibility: TaskVisibility;
23
+ dueDate: string | null;
24
+ createdByUserId: string;
25
+ createdByFullName: string;
26
+ assignedToUserId: string | null;
27
+ assignedToFullName: string | null;
28
+ createdAt: string;
29
+ updatedAt: string;
30
+ isOverdue: boolean;
31
+ pendingEditRequestCount: number;
32
+ }
33
+
34
+ export interface TaskEditRequest {
35
+ id: string;
36
+ taskId: number;
37
+ taskTitle: string;
38
+ requestedByUserId: string;
39
+ requestedByFullName: string;
40
+ requestedByEmail: string;
41
+ title: string | null;
42
+ description: string | null;
43
+ status: TaskStatus | null;
44
+ priority: TaskPriority | null;
45
+ visibility: TaskVisibility | null;
46
+ dueDate: string | null;
47
+ clearDueDate: boolean;
48
+ assignedToUserId: string | null;
49
+ assignedToFullName: string | null;
50
+ clearAssignedUser: boolean;
51
+ statusOfRequest: EditRequestStatus;
52
+ ownerNote: string | null;
53
+ createdAt: string;
54
+ resolvedAt: string | null;
55
+ }
56
+
57
+ export interface ActivityLog {
58
+ id: string;
59
+ action: string;
60
+ entityName: string;
61
+ entityId: string;
62
+ userId: string;
63
+ userFullName: string;
64
+ createdAt: string;
65
+ }
66
+
67
+ export interface AuthResponse {
68
+ token: string;
69
+ user: User;
70
+ }
71
+
72
+ export interface RegisterResponse {
73
+ message: string;
74
+ }
75
+
76
+ export interface ApiError {
77
+ message?: string;
78
+ }