mop-agent 0.1.7 → 0.1.9
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.
- package/README.md +14 -10
- package/apps/web/app/api/members/route.ts +39 -1
- package/apps/web/app/assistant/layout.tsx +12 -2
- package/apps/web/app/assistant/page.tsx +75 -109
- package/apps/web/app/brain/[projectId]/page.tsx +1 -1
- package/apps/web/app/brain/graph/page.tsx +1 -1
- package/apps/web/app/brain/layout.tsx +12 -2
- package/apps/web/app/brain/page.tsx +10 -9
- package/apps/web/app/chat/[projectId]/page.tsx +1 -1
- package/apps/web/app/chat/layout.tsx +12 -2
- package/apps/web/app/globals.css +401 -2
- package/apps/web/app/login/layout.tsx +16 -0
- package/apps/web/app/login/page.tsx +83 -0
- package/apps/web/app/settings/layout.tsx +12 -3
- package/apps/web/app/settings/page.tsx +178 -35
- package/apps/web/app/setup/layout.tsx +15 -0
- package/apps/web/app/setup/page.tsx +28 -91
- package/apps/web/app/team/layout.tsx +2 -2
- package/apps/web/app/team/page.tsx +3 -84
- package/apps/web/components/AppShell.tsx +110 -0
- package/apps/web/lib/page-auth.ts +9 -2
- package/installer/mop-agent.mjs +62 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
package/apps/web/app/globals.css
CHANGED
|
@@ -93,6 +93,365 @@ button {
|
|
|
93
93
|
|
|
94
94
|
::selection { background: #742220; color: #fef9e1; }
|
|
95
95
|
|
|
96
|
+
/* Shared protected-app shell, inspired by a compact retro server console. */
|
|
97
|
+
.mop-app-frame {
|
|
98
|
+
min-height: 100vh;
|
|
99
|
+
display: grid;
|
|
100
|
+
grid-template-columns: 238px minmax(0, 1fr);
|
|
101
|
+
grid-template-rows: 70px minmax(0, 1fr);
|
|
102
|
+
grid-template-areas:
|
|
103
|
+
"topbar topbar"
|
|
104
|
+
"sidebar main";
|
|
105
|
+
background: var(--mop-cream);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.mop-app-topbar {
|
|
109
|
+
grid-area: topbar;
|
|
110
|
+
position: sticky;
|
|
111
|
+
top: 0;
|
|
112
|
+
z-index: 50;
|
|
113
|
+
display: grid;
|
|
114
|
+
grid-template-columns: 238px minmax(0, 1fr);
|
|
115
|
+
min-width: 0;
|
|
116
|
+
color: var(--mop-cream);
|
|
117
|
+
background:
|
|
118
|
+
linear-gradient(rgba(255, 255, 255, .025), rgba(0, 0, 0, .05)),
|
|
119
|
+
var(--mop-red);
|
|
120
|
+
border-bottom: 2px solid rgba(55, 18, 17, .55);
|
|
121
|
+
box-shadow: 0 2px 0 rgba(45, 74, 62, .28);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.mop-app-brand {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: 9px;
|
|
128
|
+
min-width: 0;
|
|
129
|
+
padding: 7px 14px;
|
|
130
|
+
color: var(--mop-cream);
|
|
131
|
+
text-decoration: none;
|
|
132
|
+
border-right: 1px solid rgba(254, 249, 225, .16);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.mop-app-brand img {
|
|
136
|
+
width: 51px;
|
|
137
|
+
height: 51px;
|
|
138
|
+
object-fit: contain;
|
|
139
|
+
filter: drop-shadow(2px 3px 0 rgba(36, 20, 14, .34));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.mop-app-brand span {
|
|
143
|
+
overflow: hidden;
|
|
144
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
145
|
+
font-size: 14px;
|
|
146
|
+
font-weight: 900;
|
|
147
|
+
letter-spacing: .055em;
|
|
148
|
+
white-space: nowrap;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.mop-app-topbar-main {
|
|
152
|
+
position: relative;
|
|
153
|
+
min-width: 0;
|
|
154
|
+
display: flex;
|
|
155
|
+
align-items: center;
|
|
156
|
+
gap: 14px;
|
|
157
|
+
padding: 0 18px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.mop-menu-toggle {
|
|
161
|
+
display: none;
|
|
162
|
+
width: 37px;
|
|
163
|
+
height: 37px;
|
|
164
|
+
border: 1px solid rgba(254, 249, 225, .28);
|
|
165
|
+
background: rgba(254, 249, 225, .08);
|
|
166
|
+
color: var(--mop-cream);
|
|
167
|
+
cursor: pointer;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.mop-topbar-title {
|
|
171
|
+
display: flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
gap: 8px;
|
|
174
|
+
min-width: 130px;
|
|
175
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
176
|
+
font-size: 13px;
|
|
177
|
+
letter-spacing: .08em;
|
|
178
|
+
text-transform: uppercase;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.mop-live-dot {
|
|
182
|
+
width: 8px;
|
|
183
|
+
height: 8px;
|
|
184
|
+
background: #78e19b;
|
|
185
|
+
box-shadow: 0 0 0 3px rgba(120, 225, 155, .14);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.mop-topbar-center {
|
|
189
|
+
position: absolute;
|
|
190
|
+
left: 50%;
|
|
191
|
+
top: 50%;
|
|
192
|
+
transform: translate(-50%, -50%);
|
|
193
|
+
min-width: 255px;
|
|
194
|
+
padding: 9px 28px;
|
|
195
|
+
text-align: center;
|
|
196
|
+
color: rgba(254, 249, 225, .86);
|
|
197
|
+
border: 1px solid rgba(254, 249, 225, .12);
|
|
198
|
+
background: rgba(254, 249, 225, .07);
|
|
199
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
200
|
+
font-size: 11px;
|
|
201
|
+
font-weight: 800;
|
|
202
|
+
letter-spacing: .18em;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.mop-topbar-meta {
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
gap: 9px;
|
|
209
|
+
margin-left: auto;
|
|
210
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
211
|
+
font-size: 10px;
|
|
212
|
+
font-weight: 800;
|
|
213
|
+
letter-spacing: .12em;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.mop-version {
|
|
217
|
+
padding: 4px 6px;
|
|
218
|
+
border: 1px solid rgba(254, 249, 225, .22);
|
|
219
|
+
background: rgba(254, 249, 225, .07);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.mop-app-sidebar {
|
|
223
|
+
grid-area: sidebar;
|
|
224
|
+
position: sticky;
|
|
225
|
+
top: 70px;
|
|
226
|
+
height: calc(100vh - 70px);
|
|
227
|
+
z-index: 40;
|
|
228
|
+
display: flex;
|
|
229
|
+
flex-direction: column;
|
|
230
|
+
overflow-y: auto;
|
|
231
|
+
padding: 15px 9px 12px;
|
|
232
|
+
color: var(--mop-cream);
|
|
233
|
+
background:
|
|
234
|
+
linear-gradient(rgba(255,255,255,.018), rgba(0,0,0,.05)),
|
|
235
|
+
var(--mop-green);
|
|
236
|
+
border-right: 2px solid #20362e;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.mop-nav-section { margin-bottom: 17px; }
|
|
240
|
+
.mop-nav-section > p {
|
|
241
|
+
margin: 0 8px 9px;
|
|
242
|
+
color: rgba(254, 249, 225, .46);
|
|
243
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
244
|
+
font-size: 9px;
|
|
245
|
+
font-weight: 900;
|
|
246
|
+
letter-spacing: .22em;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.mop-nav-section nav { display: grid; gap: 4px; }
|
|
250
|
+
.mop-nav-section a {
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
gap: 11px;
|
|
254
|
+
min-height: 43px;
|
|
255
|
+
padding: 8px 12px;
|
|
256
|
+
color: rgba(254, 249, 225, .78);
|
|
257
|
+
border: 1px solid transparent;
|
|
258
|
+
text-decoration: none;
|
|
259
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
260
|
+
font-size: 12px;
|
|
261
|
+
font-weight: 800;
|
|
262
|
+
letter-spacing: .055em;
|
|
263
|
+
text-transform: uppercase;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.mop-nav-section a:hover {
|
|
267
|
+
color: var(--mop-cream);
|
|
268
|
+
background: rgba(254, 249, 225, .07);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.mop-nav-section a.is-active {
|
|
272
|
+
color: #ff8a3d;
|
|
273
|
+
border-color: rgba(254, 249, 225, .18);
|
|
274
|
+
background: rgba(254, 249, 225, .09);
|
|
275
|
+
box-shadow: inset 3px 0 #ff6f2c, 2px 2px 0 rgba(18, 38, 30, .25);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.mop-nav-icon {
|
|
279
|
+
width: 21px;
|
|
280
|
+
text-align: center;
|
|
281
|
+
color: #ff6f2c;
|
|
282
|
+
font-size: 16px;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.mop-sidebar-spacer { flex: 1; }
|
|
286
|
+
.mop-account-card {
|
|
287
|
+
width: 100%;
|
|
288
|
+
display: flex;
|
|
289
|
+
align-items: center;
|
|
290
|
+
gap: 9px;
|
|
291
|
+
padding: 10px 8px;
|
|
292
|
+
color: var(--mop-cream);
|
|
293
|
+
border: 1px solid rgba(254, 249, 225, .13);
|
|
294
|
+
background: rgba(0, 0, 0, .07);
|
|
295
|
+
text-align: left;
|
|
296
|
+
cursor: pointer;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.mop-account-avatar {
|
|
300
|
+
flex: 0 0 auto;
|
|
301
|
+
width: 31px;
|
|
302
|
+
height: 31px;
|
|
303
|
+
display: grid;
|
|
304
|
+
place-items: center;
|
|
305
|
+
background: var(--mop-red);
|
|
306
|
+
color: var(--mop-cream);
|
|
307
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
308
|
+
font-weight: 900;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.mop-account-copy {
|
|
312
|
+
min-width: 0;
|
|
313
|
+
display: grid;
|
|
314
|
+
flex: 1;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.mop-account-copy strong,
|
|
318
|
+
.mop-account-copy small {
|
|
319
|
+
overflow: hidden;
|
|
320
|
+
text-overflow: ellipsis;
|
|
321
|
+
white-space: nowrap;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.mop-account-copy strong { font-size: 12px; }
|
|
325
|
+
.mop-account-copy small { color: rgba(254, 249, 225, .52); font-size: 9px; letter-spacing: .09em; text-transform: uppercase; }
|
|
326
|
+
|
|
327
|
+
.mop-app-main {
|
|
328
|
+
grid-area: main;
|
|
329
|
+
min-width: 0;
|
|
330
|
+
min-height: calc(100vh - 70px);
|
|
331
|
+
position: relative;
|
|
332
|
+
overflow: hidden;
|
|
333
|
+
background:
|
|
334
|
+
radial-gradient(circle at 50% -20%, rgba(255, 255, 255, .95), transparent 54%),
|
|
335
|
+
var(--mop-cream);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.mop-page {
|
|
339
|
+
width: min(100%, 1180px);
|
|
340
|
+
margin: 0 auto;
|
|
341
|
+
padding: 30px clamp(18px, 3vw, 42px) 48px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.mop-page-heading {
|
|
345
|
+
display: flex;
|
|
346
|
+
align-items: flex-end;
|
|
347
|
+
justify-content: space-between;
|
|
348
|
+
gap: 16px;
|
|
349
|
+
margin-bottom: 22px;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.mop-page-kicker {
|
|
353
|
+
margin: 0 0 6px;
|
|
354
|
+
color: var(--mop-red);
|
|
355
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
356
|
+
font-size: 10px;
|
|
357
|
+
font-weight: 900;
|
|
358
|
+
letter-spacing: .18em;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.mop-page-heading h1 { margin: 0; font-family: "SFMono-Regular", Consolas, monospace; font-size: clamp(24px, 3vw, 34px); }
|
|
362
|
+
.mop-page-heading p:last-child { margin: 7px 0 0; color: rgba(45, 74, 62, .65); }
|
|
363
|
+
|
|
364
|
+
.mop-panel {
|
|
365
|
+
border: 1px solid rgba(45, 74, 62, .46);
|
|
366
|
+
border-bottom-width: 4px;
|
|
367
|
+
background: rgba(255, 253, 242, .82);
|
|
368
|
+
box-shadow: 2px 2px 0 rgba(45, 74, 62, .08);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.mop-sidebar-scrim { display: none; }
|
|
372
|
+
|
|
373
|
+
/* Assistant content now lives inside the shared shell. */
|
|
374
|
+
.mop-assistant-page {
|
|
375
|
+
min-height: calc(100vh - 70px);
|
|
376
|
+
position: relative;
|
|
377
|
+
display: flex;
|
|
378
|
+
flex-direction: column;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.mop-assistant-toolbar {
|
|
382
|
+
min-height: 55px;
|
|
383
|
+
display: flex;
|
|
384
|
+
align-items: center;
|
|
385
|
+
justify-content: space-between;
|
|
386
|
+
gap: 16px;
|
|
387
|
+
padding: 9px 22px;
|
|
388
|
+
border-bottom: 1px solid rgba(45, 74, 62, .24);
|
|
389
|
+
background: rgba(254, 249, 225, .78);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.mop-assistant-conversation { flex: 1; overflow-y: auto; padding: 0 28px; }
|
|
393
|
+
.mop-assistant-welcome {
|
|
394
|
+
min-height: calc(100vh - 285px);
|
|
395
|
+
display: flex;
|
|
396
|
+
flex-direction: column;
|
|
397
|
+
align-items: center;
|
|
398
|
+
justify-content: center;
|
|
399
|
+
padding: 32px 0 90px;
|
|
400
|
+
text-align: center;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.mop-assistant-composer-wrap {
|
|
404
|
+
position: absolute;
|
|
405
|
+
left: 0;
|
|
406
|
+
right: 0;
|
|
407
|
+
bottom: 0;
|
|
408
|
+
padding: 28px 30px 18px;
|
|
409
|
+
background: linear-gradient(transparent, var(--mop-cream) 28%);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.mop-settings-grid {
|
|
413
|
+
display: grid;
|
|
414
|
+
grid-template-columns: 210px minmax(0, 1fr);
|
|
415
|
+
gap: 18px;
|
|
416
|
+
align-items: start;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.mop-settings-nav { padding: 9px; }
|
|
420
|
+
.mop-settings-nav button {
|
|
421
|
+
width: 100%;
|
|
422
|
+
display: flex;
|
|
423
|
+
align-items: center;
|
|
424
|
+
gap: 10px;
|
|
425
|
+
padding: 12px;
|
|
426
|
+
border: 1px solid transparent;
|
|
427
|
+
background: transparent;
|
|
428
|
+
color: var(--mop-green);
|
|
429
|
+
text-align: left;
|
|
430
|
+
cursor: pointer;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.mop-settings-nav button.is-active {
|
|
434
|
+
color: var(--mop-cream);
|
|
435
|
+
border-color: var(--mop-red);
|
|
436
|
+
background: var(--mop-red);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.mop-settings-content { padding: clamp(18px, 3vw, 30px); }
|
|
440
|
+
.mop-settings-content th,
|
|
441
|
+
.mop-settings-content td {
|
|
442
|
+
padding: 11px 9px;
|
|
443
|
+
border-bottom: 1px solid rgba(45, 74, 62, .2);
|
|
444
|
+
text-align: left;
|
|
445
|
+
}
|
|
446
|
+
.mop-settings-content th {
|
|
447
|
+
color: rgba(45, 74, 62, .58);
|
|
448
|
+
font-family: "SFMono-Regular", Consolas, monospace;
|
|
449
|
+
font-size: 9px;
|
|
450
|
+
letter-spacing: .13em;
|
|
451
|
+
text-transform: uppercase;
|
|
452
|
+
}
|
|
453
|
+
.mop-assistant-welcome img { width: 100%; height: 100%; object-fit: contain; }
|
|
454
|
+
|
|
96
455
|
@media (prefers-reduced-transparency: reduce) {
|
|
97
456
|
body::before,
|
|
98
457
|
body::after { display: none; }
|
|
@@ -102,7 +461,47 @@ button {
|
|
|
102
461
|
.mop-setup-shell { grid-template-columns: 1fr !important; }
|
|
103
462
|
.mop-setup-brand { min-height: 34vh; padding: 36px !important; }
|
|
104
463
|
.mop-setup-form { border-left: 0 !important; border-top: 1px solid #2d4a3e !important; }
|
|
105
|
-
.mop-assistant-shell { grid-template-columns: 1fr !important; }
|
|
106
|
-
.mop-assistant-sidebar { display: none !important; }
|
|
107
464
|
.mop-prompt-grid { grid-template-columns: 1fr !important; }
|
|
465
|
+
|
|
466
|
+
.mop-app-frame {
|
|
467
|
+
grid-template-columns: 1fr;
|
|
468
|
+
grid-template-rows: 62px minmax(0, 1fr);
|
|
469
|
+
grid-template-areas: "topbar" "main";
|
|
470
|
+
}
|
|
471
|
+
.mop-app-topbar { grid-template-columns: 66px minmax(0, 1fr); }
|
|
472
|
+
.mop-app-brand { justify-content: center; padding: 5px; }
|
|
473
|
+
.mop-app-brand img { width: 49px; height: 49px; }
|
|
474
|
+
.mop-app-brand span, .mop-topbar-center { display: none; }
|
|
475
|
+
.mop-app-topbar-main { padding: 0 10px; gap: 9px; }
|
|
476
|
+
.mop-menu-toggle { display: block; }
|
|
477
|
+
.mop-topbar-title { min-width: 0; font-size: 11px; }
|
|
478
|
+
.mop-topbar-meta > span:first-child { display: none; }
|
|
479
|
+
.mop-app-sidebar {
|
|
480
|
+
position: fixed;
|
|
481
|
+
top: 62px;
|
|
482
|
+
left: 0;
|
|
483
|
+
bottom: 0;
|
|
484
|
+
width: min(82vw, 280px);
|
|
485
|
+
height: auto;
|
|
486
|
+
transform: translateX(-105%);
|
|
487
|
+
transition: transform 160ms steps(4, end);
|
|
488
|
+
}
|
|
489
|
+
.mop-app-sidebar.is-open { transform: translateX(0); }
|
|
490
|
+
.mop-sidebar-scrim {
|
|
491
|
+
display: block;
|
|
492
|
+
position: fixed;
|
|
493
|
+
inset: 62px 0 0;
|
|
494
|
+
z-index: 35;
|
|
495
|
+
border: 0;
|
|
496
|
+
background: rgba(20, 34, 29, .56);
|
|
497
|
+
}
|
|
498
|
+
.mop-app-main { min-height: calc(100vh - 62px); }
|
|
499
|
+
.mop-assistant-page { min-height: calc(100vh - 62px); }
|
|
500
|
+
.mop-assistant-toolbar { align-items: flex-start; flex-direction: column; }
|
|
501
|
+
.mop-assistant-conversation { padding: 0 16px; }
|
|
502
|
+
.mop-assistant-composer-wrap { padding: 26px 12px 12px; }
|
|
503
|
+
.mop-settings-grid { grid-template-columns: 1fr; }
|
|
504
|
+
.mop-settings-nav { display: flex; gap: 7px; }
|
|
505
|
+
.mop-settings-nav button { justify-content: center; }
|
|
506
|
+
.mop-user-invite-form { grid-template-columns: 1fr !important; }
|
|
108
507
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { unstable_noStore as noStore } from "next/cache";
|
|
3
|
+
import { headers } from "next/headers";
|
|
4
|
+
import { redirect } from "next/navigation";
|
|
5
|
+
import { auth, ownerExists } from "@/lib/auth";
|
|
6
|
+
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
8
|
+
export const revalidate = 0;
|
|
9
|
+
|
|
10
|
+
export default async function LoginLayout({ children }: { children: ReactNode }) {
|
|
11
|
+
noStore();
|
|
12
|
+
if (!ownerExists()) redirect("/setup");
|
|
13
|
+
const session = await auth.api.getSession({ headers: await headers() });
|
|
14
|
+
if (session) redirect("/assistant");
|
|
15
|
+
return children;
|
|
16
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { signIn } from "@/lib/auth-client";
|
|
5
|
+
|
|
6
|
+
type SetupStatus = { authenticated: boolean };
|
|
7
|
+
|
|
8
|
+
async function waitForSession(): Promise<boolean> {
|
|
9
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetch(`/api/setup/status?t=${Date.now()}`, { cache: "no-store", credentials: "include" });
|
|
12
|
+
const status = await response.json() as SetupStatus;
|
|
13
|
+
if (status.authenticated) return true;
|
|
14
|
+
} catch {
|
|
15
|
+
// Retry a short proxy/session propagation gap.
|
|
16
|
+
}
|
|
17
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function LoginPage() {
|
|
23
|
+
const [email, setEmail] = useState("");
|
|
24
|
+
const [password, setPassword] = useState("");
|
|
25
|
+
const [msg, setMsg] = useState("");
|
|
26
|
+
const [busy, setBusy] = useState(false);
|
|
27
|
+
|
|
28
|
+
async function submit(e: React.FormEvent) {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
setBusy(true);
|
|
31
|
+
setMsg("");
|
|
32
|
+
const result = await signIn.email({ email, password });
|
|
33
|
+
if (result.error) {
|
|
34
|
+
setMsg(result.error.message ?? "Sign in failed.");
|
|
35
|
+
setBusy(false);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (await waitForSession()) {
|
|
39
|
+
window.location.replace(`/assistant?auth=${Date.now()}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
setMsg("Sign in succeeded, but the session cookie was not accepted. Use the configured domain and HTTPS address.");
|
|
43
|
+
setBusy(false);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<main className="mop-setup-shell" style={shell}>
|
|
48
|
+
<section className="mop-setup-brand" style={brandPanel}>
|
|
49
|
+
<img src="/icon.svg" alt="MOP-AGENT" style={heroLogo} />
|
|
50
|
+
</section>
|
|
51
|
+
<section className="mop-setup-form" style={formWrap}>
|
|
52
|
+
<div style={formCard}>
|
|
53
|
+
<p style={eyebrow}>WELCOME BACK</p>
|
|
54
|
+
<h1 style={{ fontSize: 27, margin: "8px 0" }}>Sign in to MOP-AGENT</h1>
|
|
55
|
+
<p style={description}>Use the account created for you by the Administrator.</p>
|
|
56
|
+
<form onSubmit={submit} style={{ display: "grid", gap: 14, marginTop: 28 }}>
|
|
57
|
+
<label style={label}>Email
|
|
58
|
+
<input placeholder="you@example.com" type="email" autoComplete="email" required value={email} onChange={(e) => setEmail(e.target.value)} style={inputStyle} />
|
|
59
|
+
</label>
|
|
60
|
+
<label style={label}>Password
|
|
61
|
+
<input placeholder="Your password" type="password" autoComplete="current-password" required value={password} onChange={(e) => setPassword(e.target.value)} style={inputStyle} />
|
|
62
|
+
</label>
|
|
63
|
+
<button type="submit" disabled={busy} style={{ ...buttonStyle, opacity: busy ? 0.65 : 1 }}>
|
|
64
|
+
{busy ? "Signing in…" : "Sign in"}
|
|
65
|
+
</button>
|
|
66
|
+
</form>
|
|
67
|
+
{msg && <p role="alert" style={{ marginTop: 16, color: "#742220" }}>{msg}</p>}
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
</main>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const shell: React.CSSProperties = { minHeight: "100vh", display: "grid", gridTemplateColumns: "minmax(0, 1.25fr) minmax(380px, .75fr)", background: "#fef9e1" };
|
|
75
|
+
const brandPanel: React.CSSProperties = { padding: "clamp(48px, 8vw, 110px)", display: "flex", alignItems: "center", justifyContent: "center", overflow: "hidden", background: "radial-gradient(circle at 50% 45%, #49685c 0, #2d4a3e 58%, #21382f 100%)" };
|
|
76
|
+
const heroLogo: React.CSSProperties = { display: "block", width: "min(72%, 560px)", height: "auto", maxHeight: "72vh", objectFit: "contain", filter: "drop-shadow(0 24px 34px rgba(0,0,0,.28))" };
|
|
77
|
+
const formWrap: React.CSSProperties = { display: "flex", alignItems: "center", justifyContent: "center", padding: 28, background: "#fef9e1", borderLeft: "1px solid #2d4a3e" };
|
|
78
|
+
const formCard: React.CSSProperties = { width: "min(100%, 430px)", padding: "38px 34px", border: "1px solid rgba(45,74,62,.45)", borderRadius: 18, background: "#fffdf2", boxShadow: "0 24px 70px rgba(45,74,62,.14)" };
|
|
79
|
+
const eyebrow: React.CSSProperties = { margin: "20px 0 0", color: "#742220", fontSize: 12, fontWeight: 800, letterSpacing: ".16em" };
|
|
80
|
+
const description: React.CSSProperties = { color: "#7f8da2", lineHeight: 1.55, marginTop: 0 };
|
|
81
|
+
const label: React.CSSProperties = { display: "grid", gap: 7, color: "#2d4a3e", fontSize: 13, fontWeight: 650 };
|
|
82
|
+
const inputStyle: React.CSSProperties = { padding: "12px 13px", borderRadius: 9, border: "1px solid rgba(45,74,62,.42)", outline: "none", background: "#fef9e1", color: "#2d4a3e", fontSize: 15 };
|
|
83
|
+
const buttonStyle: React.CSSProperties = { marginTop: 4, padding: "12px 14px", borderRadius: 9, border: "1px solid #742220", background: "#742220", color: "#fef9e1", fontWeight: 750, fontSize: 15, cursor: "pointer" };
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { AppShell } from "@/components/AppShell";
|
|
3
|
+
import { requireOwnerPage } from "@/lib/page-auth";
|
|
3
4
|
|
|
4
5
|
export const dynamic = "force-dynamic";
|
|
5
6
|
export const revalidate = 0;
|
|
6
7
|
|
|
7
8
|
export default async function SettingsLayout({ children }: { children: ReactNode }) {
|
|
8
|
-
await
|
|
9
|
-
return
|
|
9
|
+
const session = await requireOwnerPage();
|
|
10
|
+
return (
|
|
11
|
+
<AppShell viewer={{
|
|
12
|
+
name: session.user.name || session.user.email,
|
|
13
|
+
email: session.user.email,
|
|
14
|
+
role: "owner",
|
|
15
|
+
}}>
|
|
16
|
+
{children}
|
|
17
|
+
</AppShell>
|
|
18
|
+
);
|
|
10
19
|
}
|