@allbluecn/web-app 0.11.0 → 0.11.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.
- package/package.json +6 -3
- package/public/favicon-dark.svg +1 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +1 -0
- package/public/logo/allblue-logo-dark.png +0 -0
- package/public/logo/allblue-logo-light.png +0 -0
- package/src/__tests__/integration/login-email-acceptance.test.ts +202 -0
- package/src/__tests__/integration/login-email-p2-acceptance.test.ts +142 -0
- package/src/components/auth/icon-input.tsx +9 -1
- package/src/components/auth/password-input.tsx +9 -1
- package/src/components/changelog/changelog-page.tsx +44 -1
- package/src/components/forms/app-field.tsx +3 -1
- package/src/components/knowledge/knowledge-toolbar.tsx +17 -21
- package/src/components/layout/sidebar-03/app-sidebar.tsx +4 -15
- package/src/components/notifications/settings-notifications.tsx +41 -1
- package/src/components/settings/personal-team/account/settings-profile.tsx +68 -1
- package/src/lib/__tests__/edition-desktop.test.ts +23 -0
- package/src/lib/app-version.ts +1 -1
- package/src/lib/auth/auth.config.ts +123 -99
- package/src/lib/changelog/sdk-versions.ts +4 -4
- package/src/lib/edition-desktop.ts +22 -0
- package/src/lib/session.ts +2 -2
- package/src/routeTree.gen.ts +114 -0
- package/src/routes/__root.tsx +20 -3
- package/src/routes/__tests__/-auth-schemas.test.ts +62 -0
- package/src/routes/__tests__/-edition-routing.test.tsx +14 -1
- package/src/routes/__tests__/route-tree-structure.test.ts +1 -0
- package/src/routes/_login/changelog/confirm/route.lazy.tsx +72 -0
- package/src/routes/_login/changelog/confirm/route.tsx +28 -0
- package/src/routes/_login/email-unsubscribe/route.lazy.tsx +76 -0
- package/src/routes/_login/email-unsubscribe/route.tsx +28 -0
- package/src/routes/_login/forgot-password/route.lazy.tsx +2 -1
- package/src/routes/_login/login/route.lazy.tsx +4 -2
- package/src/routes/_login/register/route.lazy.tsx +28 -17
- package/src/routes/_login/register/route.tsx +8 -1
- package/src/routes/_login/reset-password/route.lazy.tsx +4 -2
- package/src/routes/_login/schemas/-auth.ts +30 -7
- package/src/routes/_login/verify-email/route.lazy.tsx +82 -0
- package/src/routes/_login/verify-email/route.tsx +28 -0
- package/src/routes/_nav/route.tsx +5 -0
- package/src/routes/_public/index/route.tsx +2 -1
- package/src/routes/api/auth/desktop-callback.ts +2 -2
- package/src/routes/api/billing-webhook.ts +4 -0
- package/src/routes/api/cron/changelog-release.ts +100 -0
- package/src/routes/api/cron/subscription-reminders.ts +110 -0
- package/src/routes/api.auth.$.ts +3 -5
- package/src/server/__tests__/auth-registration.test.ts +60 -0
- package/src/server/auth-registration.ts +40 -0
- package/src/server/billing-email.ts +39 -0
- package/src/server/email/__tests__/deliverability.test.ts +68 -0
- package/src/server/email/__tests__/index.test.ts +32 -0
- package/src/server/email/__tests__/outbox.test.tsx +129 -0
- package/src/server/email/__tests__/product-templates.test.tsx +106 -0
- package/src/server/email/__tests__/registry.test.tsx +50 -0
- package/src/server/email/deliverability.ts +57 -0
- package/src/server/email/drivers/console.ts +11 -0
- package/src/server/email/drivers/resend.ts +31 -0
- package/src/server/email/drivers/smtp.ts +27 -0
- package/src/server/email/index.ts +56 -0
- package/src/server/email/outbox.ts +141 -0
- package/src/server/email/preferences.ts +20 -0
- package/src/server/email/registry.ts +38 -0
- package/src/server/email/sender.ts +34 -17
- package/src/server/email/templates/_base/button.tsx +12 -0
- package/src/server/email/templates/_base/email-layout.tsx +117 -0
- package/src/server/email/templates/_base/theme-fonts.tsx +43 -0
- package/src/server/email/templates/_base/theme.ts +22 -0
- package/src/server/email/templates/auth/reset-password.tsx +58 -0
- package/src/server/email/templates/auth/verify-email.tsx +59 -0
- package/src/server/email/templates/auth/welcome.tsx +49 -0
- package/src/server/email/templates/billing/payment-receipt.tsx +51 -0
- package/src/server/email/templates/billing/subscription-canceled.tsx +51 -0
- package/src/server/email/templates/billing/subscription-ending.tsx +51 -0
- package/src/server/email/templates/notification/mirror.tsx +50 -0
- package/src/server/email/templates/product/changelog-confirm.tsx +53 -0
- package/src/server/email/templates/product/changelog-release.tsx +65 -0
- package/src/server/email/templates/team/apply-notify.tsx +53 -0
- package/src/server/email/templates/team/approved.tsx +53 -0
- package/src/server/email/templates/team/invite.tsx +57 -0
- package/src/server/middlewares/security-headers.ts +11 -2
- package/src/server/notifications/__tests__/email-mirror.test.ts +193 -0
- package/src/server/notifications/__tests__/verify-reminder.test.ts +94 -0
- package/src/server/notifications/notification-service.ts +71 -1
- package/src/server/notifications/verify-reminder.ts +55 -0
- package/src/server/oauth-config.ts +59 -0
- package/src/server/serverFns/__tests__/auth-server-schemas.test.ts +168 -0
- package/src/server/serverFns/__tests__/email-verification.test.ts +149 -0
- package/src/server/serverFns/__tests__/team-invitation.test.ts +2 -2
- package/src/server/serverFns/auth/email-verification.ts +132 -0
- package/src/server/serverFns/auth/forgot-password.ts +22 -2
- package/src/server/serverFns/auth/providers.ts +4 -3
- package/src/server/serverFns/auth/register.ts +63 -19
- package/src/server/serverFns/auth/registration-mode.ts +23 -0
- package/src/server/serverFns/auth/reset-password.ts +1 -1
- package/src/server/serverFns/auth/{invitation.ts → verify-reminder.ts} +9 -26
- package/src/server/serverFns/changelog/__tests__/subscribe.test.ts +119 -0
- package/src/server/serverFns/changelog/subscribe.ts +89 -0
- package/src/server/serverFns/email-unsubscribe.ts +66 -0
- package/src/server/serverFns/notifications.ts +10 -0
- package/src/server/serverFns/team.ts +49 -11
- package/vite.shared.ts +87 -3
package/src/routeTree.gen.ts
CHANGED
|
@@ -18,10 +18,12 @@ import { Route as DocsRouteImport } from './routes/docs'
|
|
|
18
18
|
import { Route as ShareRouteRouteImport } from './routes/share/route'
|
|
19
19
|
import { Route as WorkspaceRouteImport } from './routes/workspace'
|
|
20
20
|
import { Route as _desktopOnboardingRouteRouteImport } from './routes/__desktop/onboarding/route'
|
|
21
|
+
import { Route as LoginEmailUnsubscribeRouteRouteImport } from './routes/_login/email-unsubscribe/route'
|
|
21
22
|
import { Route as LoginForgotPasswordRouteRouteImport } from './routes/_login/forgot-password/route'
|
|
22
23
|
import { Route as LoginLoginRouteRouteImport } from './routes/_login/login/route'
|
|
23
24
|
import { Route as LoginRegisterRouteRouteImport } from './routes/_login/register/route'
|
|
24
25
|
import { Route as LoginResetPasswordRouteRouteImport } from './routes/_login/reset-password/route'
|
|
26
|
+
import { Route as LoginVerifyEmailRouteRouteImport } from './routes/_login/verify-email/route'
|
|
25
27
|
import { Route as NavAcceptanceCriteriaRouteRouteImport } from './routes/_nav/acceptance-criteria/route'
|
|
26
28
|
import { Route as NavAgentsRouteRouteImport } from './routes/_nav/agents/route'
|
|
27
29
|
import { Route as NavAppsRouteRouteImport } from './routes/_nav/apps/route'
|
|
@@ -54,6 +56,7 @@ import { Route as ServerAiStreamRouteImport } from './routes/server.ai-stream'
|
|
|
54
56
|
import { Route as ServerWsRouteImport } from './routes/server.ws'
|
|
55
57
|
import { Route as ShareVersionIdRouteRouteImport } from './routes/share/$versionId/route'
|
|
56
58
|
import { Route as LangDocsSplatRouteRouteImport } from './routes/$lang/docs/$/route'
|
|
59
|
+
import { Route as LoginChangelogConfirmRouteRouteImport } from './routes/_login/changelog/confirm/route'
|
|
57
60
|
import { Route as NavAppsAnyhubRouteRouteImport } from './routes/_nav/apps/anyhub/route'
|
|
58
61
|
import { Route as NavChatThreadIdRouteImport } from './routes/_nav/chat/$threadId'
|
|
59
62
|
import { Route as NavDesignIndexRouteImport } from './routes/_nav/design/index'
|
|
@@ -88,6 +91,8 @@ import { Route as NavWorkspacesWorkspaceIdRouteRouteImport } from './routes/_nav
|
|
|
88
91
|
import { Route as ApiAttachmentsUploadRouteImport } from './routes/api/attachments/upload'
|
|
89
92
|
import { Route as ApiAuthSplatRouteImport } from './routes/api.auth.$'
|
|
90
93
|
import { Route as ApiAuthDesktopCallbackRouteImport } from './routes/api/auth/desktop-callback'
|
|
94
|
+
import { Route as ApiCronChangelogReleaseRouteImport } from './routes/api/cron/changelog-release'
|
|
95
|
+
import { Route as ApiCronSubscriptionRemindersRouteImport } from './routes/api/cron/subscription-reminders'
|
|
91
96
|
import { Route as ApiDesignUploadRouteImport } from './routes/api/design/upload'
|
|
92
97
|
import { Route as ApiKnowledgeSyncGithubRouteImport } from './routes/api/knowledge/sync-github'
|
|
93
98
|
import { Route as ApiLicenseActivateRouteRouteImport } from './routes/api/license/activate/route'
|
|
@@ -187,6 +192,14 @@ const _desktopOnboardingRouteRoute = _desktopOnboardingRouteRouteImport
|
|
|
187
192
|
.lazy(() =>
|
|
188
193
|
import('./routes/__desktop/onboarding/route.lazy').then((d) => d.Route),
|
|
189
194
|
)
|
|
195
|
+
const LoginEmailUnsubscribeRouteRoute =
|
|
196
|
+
LoginEmailUnsubscribeRouteRouteImport.update({
|
|
197
|
+
id: '/email-unsubscribe',
|
|
198
|
+
path: '/email-unsubscribe',
|
|
199
|
+
getParentRoute: () => LoginRouteRoute,
|
|
200
|
+
} as any).lazy(() =>
|
|
201
|
+
import('./routes/_login/email-unsubscribe/route.lazy').then((d) => d.Route),
|
|
202
|
+
)
|
|
190
203
|
const LoginForgotPasswordRouteRoute =
|
|
191
204
|
LoginForgotPasswordRouteRouteImport.update({
|
|
192
205
|
id: '/forgot-password',
|
|
@@ -216,6 +229,13 @@ const LoginResetPasswordRouteRoute = LoginResetPasswordRouteRouteImport.update({
|
|
|
216
229
|
} as any).lazy(() =>
|
|
217
230
|
import('./routes/_login/reset-password/route.lazy').then((d) => d.Route),
|
|
218
231
|
)
|
|
232
|
+
const LoginVerifyEmailRouteRoute = LoginVerifyEmailRouteRouteImport.update({
|
|
233
|
+
id: '/verify-email',
|
|
234
|
+
path: '/verify-email',
|
|
235
|
+
getParentRoute: () => LoginRouteRoute,
|
|
236
|
+
} as any).lazy(() =>
|
|
237
|
+
import('./routes/_login/verify-email/route.lazy').then((d) => d.Route),
|
|
238
|
+
)
|
|
219
239
|
const NavAcceptanceCriteriaRouteRoute =
|
|
220
240
|
NavAcceptanceCriteriaRouteRouteImport.update({
|
|
221
241
|
id: '/acceptance-criteria',
|
|
@@ -395,6 +415,14 @@ const LangDocsSplatRouteRoute = LangDocsSplatRouteRouteImport.update({
|
|
|
395
415
|
} as any).lazy(() =>
|
|
396
416
|
import('./routes/$lang/docs/$/route.lazy').then((d) => d.Route),
|
|
397
417
|
)
|
|
418
|
+
const LoginChangelogConfirmRouteRoute =
|
|
419
|
+
LoginChangelogConfirmRouteRouteImport.update({
|
|
420
|
+
id: '/changelog/confirm',
|
|
421
|
+
path: '/changelog/confirm',
|
|
422
|
+
getParentRoute: () => LoginRouteRoute,
|
|
423
|
+
} as any).lazy(() =>
|
|
424
|
+
import('./routes/_login/changelog/confirm/route.lazy').then((d) => d.Route),
|
|
425
|
+
)
|
|
398
426
|
const NavAppsAnyhubRouteRoute = NavAppsAnyhubRouteRouteImport.update({
|
|
399
427
|
id: '/anyhub',
|
|
400
428
|
path: '/anyhub',
|
|
@@ -615,6 +643,17 @@ const ApiAuthDesktopCallbackRoute = ApiAuthDesktopCallbackRouteImport.update({
|
|
|
615
643
|
path: '/api/auth/desktop-callback',
|
|
616
644
|
getParentRoute: () => rootRouteImport,
|
|
617
645
|
} as any)
|
|
646
|
+
const ApiCronChangelogReleaseRoute = ApiCronChangelogReleaseRouteImport.update({
|
|
647
|
+
id: '/api/cron/changelog-release',
|
|
648
|
+
path: '/api/cron/changelog-release',
|
|
649
|
+
getParentRoute: () => rootRouteImport,
|
|
650
|
+
} as any)
|
|
651
|
+
const ApiCronSubscriptionRemindersRoute =
|
|
652
|
+
ApiCronSubscriptionRemindersRouteImport.update({
|
|
653
|
+
id: '/api/cron/subscription-reminders',
|
|
654
|
+
path: '/api/cron/subscription-reminders',
|
|
655
|
+
getParentRoute: () => rootRouteImport,
|
|
656
|
+
} as any)
|
|
618
657
|
const ApiDesignUploadRoute = ApiDesignUploadRouteImport.update({
|
|
619
658
|
id: '/api/design/upload',
|
|
620
659
|
path: '/api/design/upload',
|
|
@@ -901,10 +940,12 @@ export interface FileRoutesByFullPath {
|
|
|
901
940
|
'/docs': typeof DocsRoute
|
|
902
941
|
'/workspace': typeof WorkspaceRoute
|
|
903
942
|
'/onboarding': typeof _desktopOnboardingRouteRoute
|
|
943
|
+
'/email-unsubscribe': typeof LoginEmailUnsubscribeRouteRoute
|
|
904
944
|
'/forgot-password': typeof LoginForgotPasswordRouteRoute
|
|
905
945
|
'/login': typeof LoginLoginRouteRoute
|
|
906
946
|
'/register': typeof LoginRegisterRouteRoute
|
|
907
947
|
'/reset-password': typeof LoginResetPasswordRouteRoute
|
|
948
|
+
'/verify-email': typeof LoginVerifyEmailRouteRoute
|
|
908
949
|
'/acceptance-criteria': typeof NavAcceptanceCriteriaRouteRoute
|
|
909
950
|
'/agents': typeof NavAgentsRouteRoute
|
|
910
951
|
'/apps': typeof NavAppsRouteRouteWithChildren
|
|
@@ -936,6 +977,7 @@ export interface FileRoutesByFullPath {
|
|
|
936
977
|
'/server/ai-stream': typeof ServerAiStreamRoute
|
|
937
978
|
'/server/ws': typeof ServerWsRoute
|
|
938
979
|
'/$lang/docs/$': typeof LangDocsSplatRouteRoute
|
|
980
|
+
'/changelog/confirm': typeof LoginChangelogConfirmRouteRoute
|
|
939
981
|
'/apps/anyhub': typeof NavAppsAnyhubRouteRouteWithChildren
|
|
940
982
|
'/design/$projectId': typeof NavDesignProjectIdRouteRouteWithChildren
|
|
941
983
|
'/knowledge/$kbId': typeof NavKnowledgeKbIdRouteRouteWithChildren
|
|
@@ -969,6 +1011,8 @@ export interface FileRoutesByFullPath {
|
|
|
969
1011
|
'/api/attachments/upload': typeof ApiAttachmentsUploadRoute
|
|
970
1012
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
971
1013
|
'/api/auth/desktop-callback': typeof ApiAuthDesktopCallbackRoute
|
|
1014
|
+
'/api/cron/changelog-release': typeof ApiCronChangelogReleaseRoute
|
|
1015
|
+
'/api/cron/subscription-reminders': typeof ApiCronSubscriptionRemindersRoute
|
|
972
1016
|
'/api/design/upload': typeof ApiDesignUploadRoute
|
|
973
1017
|
'/api/knowledge/sync-github': typeof ApiKnowledgeSyncGithubRoute
|
|
974
1018
|
'/api/notifications/stream': typeof ApiNotificationsStreamRoute
|
|
@@ -1013,10 +1057,12 @@ export interface FileRoutesByTo {
|
|
|
1013
1057
|
'/docs': typeof DocsRoute
|
|
1014
1058
|
'/workspace': typeof WorkspaceRoute
|
|
1015
1059
|
'/onboarding': typeof _desktopOnboardingRouteRoute
|
|
1060
|
+
'/email-unsubscribe': typeof LoginEmailUnsubscribeRouteRoute
|
|
1016
1061
|
'/forgot-password': typeof LoginForgotPasswordRouteRoute
|
|
1017
1062
|
'/login': typeof LoginLoginRouteRoute
|
|
1018
1063
|
'/register': typeof LoginRegisterRouteRoute
|
|
1019
1064
|
'/reset-password': typeof LoginResetPasswordRouteRoute
|
|
1065
|
+
'/verify-email': typeof LoginVerifyEmailRouteRoute
|
|
1020
1066
|
'/acceptance-criteria': typeof NavAcceptanceCriteriaRouteRoute
|
|
1021
1067
|
'/agents': typeof NavAgentsRouteRoute
|
|
1022
1068
|
'/apps': typeof NavAppsRouteRouteWithChildren
|
|
@@ -1042,6 +1088,7 @@ export interface FileRoutesByTo {
|
|
|
1042
1088
|
'/server/ai-stream': typeof ServerAiStreamRoute
|
|
1043
1089
|
'/server/ws': typeof ServerWsRoute
|
|
1044
1090
|
'/$lang/docs/$': typeof LangDocsSplatRouteRoute
|
|
1091
|
+
'/changelog/confirm': typeof LoginChangelogConfirmRouteRoute
|
|
1045
1092
|
'/apps/anyhub': typeof NavAppsAnyhubRouteRouteWithChildren
|
|
1046
1093
|
'/design/$projectId': typeof NavDesignProjectIdRouteRouteWithChildren
|
|
1047
1094
|
'/prd/$id': typeof NavPrdIdRouteRoute
|
|
@@ -1067,6 +1114,8 @@ export interface FileRoutesByTo {
|
|
|
1067
1114
|
'/api/attachments/upload': typeof ApiAttachmentsUploadRoute
|
|
1068
1115
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
1069
1116
|
'/api/auth/desktop-callback': typeof ApiAuthDesktopCallbackRoute
|
|
1117
|
+
'/api/cron/changelog-release': typeof ApiCronChangelogReleaseRoute
|
|
1118
|
+
'/api/cron/subscription-reminders': typeof ApiCronSubscriptionRemindersRoute
|
|
1070
1119
|
'/api/design/upload': typeof ApiDesignUploadRoute
|
|
1071
1120
|
'/api/knowledge/sync-github': typeof ApiKnowledgeSyncGithubRoute
|
|
1072
1121
|
'/api/notifications/stream': typeof ApiNotificationsStreamRoute
|
|
@@ -1115,10 +1164,12 @@ export interface FileRoutesById {
|
|
|
1115
1164
|
'/workspace': typeof WorkspaceRoute
|
|
1116
1165
|
'/_public/': typeof PublicIndexRouteRoute
|
|
1117
1166
|
'/__desktop/onboarding': typeof _desktopOnboardingRouteRoute
|
|
1167
|
+
'/_login/email-unsubscribe': typeof LoginEmailUnsubscribeRouteRoute
|
|
1118
1168
|
'/_login/forgot-password': typeof LoginForgotPasswordRouteRoute
|
|
1119
1169
|
'/_login/login': typeof LoginLoginRouteRoute
|
|
1120
1170
|
'/_login/register': typeof LoginRegisterRouteRoute
|
|
1121
1171
|
'/_login/reset-password': typeof LoginResetPasswordRouteRoute
|
|
1172
|
+
'/_login/verify-email': typeof LoginVerifyEmailRouteRoute
|
|
1122
1173
|
'/_nav/acceptance-criteria': typeof NavAcceptanceCriteriaRouteRoute
|
|
1123
1174
|
'/_nav/agents': typeof NavAgentsRouteRoute
|
|
1124
1175
|
'/_nav/apps': typeof NavAppsRouteRouteWithChildren
|
|
@@ -1150,6 +1201,7 @@ export interface FileRoutesById {
|
|
|
1150
1201
|
'/server/ai-stream': typeof ServerAiStreamRoute
|
|
1151
1202
|
'/server/ws': typeof ServerWsRoute
|
|
1152
1203
|
'/$lang/docs/$': typeof LangDocsSplatRouteRoute
|
|
1204
|
+
'/_login/changelog/confirm': typeof LoginChangelogConfirmRouteRoute
|
|
1153
1205
|
'/_nav/apps/anyhub': typeof NavAppsAnyhubRouteRouteWithChildren
|
|
1154
1206
|
'/_nav/design/$projectId': typeof NavDesignProjectIdRouteRouteWithChildren
|
|
1155
1207
|
'/_nav/knowledge/$kbId': typeof NavKnowledgeKbIdRouteRouteWithChildren
|
|
@@ -1183,6 +1235,8 @@ export interface FileRoutesById {
|
|
|
1183
1235
|
'/api/attachments/upload': typeof ApiAttachmentsUploadRoute
|
|
1184
1236
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
1185
1237
|
'/api/auth/desktop-callback': typeof ApiAuthDesktopCallbackRoute
|
|
1238
|
+
'/api/cron/changelog-release': typeof ApiCronChangelogReleaseRoute
|
|
1239
|
+
'/api/cron/subscription-reminders': typeof ApiCronSubscriptionRemindersRoute
|
|
1186
1240
|
'/api/design/upload': typeof ApiDesignUploadRoute
|
|
1187
1241
|
'/api/knowledge/sync-github': typeof ApiKnowledgeSyncGithubRoute
|
|
1188
1242
|
'/api/notifications/stream': typeof ApiNotificationsStreamRoute
|
|
@@ -1229,10 +1283,12 @@ export interface FileRouteTypes {
|
|
|
1229
1283
|
| '/docs'
|
|
1230
1284
|
| '/workspace'
|
|
1231
1285
|
| '/onboarding'
|
|
1286
|
+
| '/email-unsubscribe'
|
|
1232
1287
|
| '/forgot-password'
|
|
1233
1288
|
| '/login'
|
|
1234
1289
|
| '/register'
|
|
1235
1290
|
| '/reset-password'
|
|
1291
|
+
| '/verify-email'
|
|
1236
1292
|
| '/acceptance-criteria'
|
|
1237
1293
|
| '/agents'
|
|
1238
1294
|
| '/apps'
|
|
@@ -1264,6 +1320,7 @@ export interface FileRouteTypes {
|
|
|
1264
1320
|
| '/server/ai-stream'
|
|
1265
1321
|
| '/server/ws'
|
|
1266
1322
|
| '/$lang/docs/$'
|
|
1323
|
+
| '/changelog/confirm'
|
|
1267
1324
|
| '/apps/anyhub'
|
|
1268
1325
|
| '/design/$projectId'
|
|
1269
1326
|
| '/knowledge/$kbId'
|
|
@@ -1297,6 +1354,8 @@ export interface FileRouteTypes {
|
|
|
1297
1354
|
| '/api/attachments/upload'
|
|
1298
1355
|
| '/api/auth/$'
|
|
1299
1356
|
| '/api/auth/desktop-callback'
|
|
1357
|
+
| '/api/cron/changelog-release'
|
|
1358
|
+
| '/api/cron/subscription-reminders'
|
|
1300
1359
|
| '/api/design/upload'
|
|
1301
1360
|
| '/api/knowledge/sync-github'
|
|
1302
1361
|
| '/api/notifications/stream'
|
|
@@ -1341,10 +1400,12 @@ export interface FileRouteTypes {
|
|
|
1341
1400
|
| '/docs'
|
|
1342
1401
|
| '/workspace'
|
|
1343
1402
|
| '/onboarding'
|
|
1403
|
+
| '/email-unsubscribe'
|
|
1344
1404
|
| '/forgot-password'
|
|
1345
1405
|
| '/login'
|
|
1346
1406
|
| '/register'
|
|
1347
1407
|
| '/reset-password'
|
|
1408
|
+
| '/verify-email'
|
|
1348
1409
|
| '/acceptance-criteria'
|
|
1349
1410
|
| '/agents'
|
|
1350
1411
|
| '/apps'
|
|
@@ -1370,6 +1431,7 @@ export interface FileRouteTypes {
|
|
|
1370
1431
|
| '/server/ai-stream'
|
|
1371
1432
|
| '/server/ws'
|
|
1372
1433
|
| '/$lang/docs/$'
|
|
1434
|
+
| '/changelog/confirm'
|
|
1373
1435
|
| '/apps/anyhub'
|
|
1374
1436
|
| '/design/$projectId'
|
|
1375
1437
|
| '/prd/$id'
|
|
@@ -1395,6 +1457,8 @@ export interface FileRouteTypes {
|
|
|
1395
1457
|
| '/api/attachments/upload'
|
|
1396
1458
|
| '/api/auth/$'
|
|
1397
1459
|
| '/api/auth/desktop-callback'
|
|
1460
|
+
| '/api/cron/changelog-release'
|
|
1461
|
+
| '/api/cron/subscription-reminders'
|
|
1398
1462
|
| '/api/design/upload'
|
|
1399
1463
|
| '/api/knowledge/sync-github'
|
|
1400
1464
|
| '/api/notifications/stream'
|
|
@@ -1442,10 +1506,12 @@ export interface FileRouteTypes {
|
|
|
1442
1506
|
| '/workspace'
|
|
1443
1507
|
| '/_public/'
|
|
1444
1508
|
| '/__desktop/onboarding'
|
|
1509
|
+
| '/_login/email-unsubscribe'
|
|
1445
1510
|
| '/_login/forgot-password'
|
|
1446
1511
|
| '/_login/login'
|
|
1447
1512
|
| '/_login/register'
|
|
1448
1513
|
| '/_login/reset-password'
|
|
1514
|
+
| '/_login/verify-email'
|
|
1449
1515
|
| '/_nav/acceptance-criteria'
|
|
1450
1516
|
| '/_nav/agents'
|
|
1451
1517
|
| '/_nav/apps'
|
|
@@ -1477,6 +1543,7 @@ export interface FileRouteTypes {
|
|
|
1477
1543
|
| '/server/ai-stream'
|
|
1478
1544
|
| '/server/ws'
|
|
1479
1545
|
| '/$lang/docs/$'
|
|
1546
|
+
| '/_login/changelog/confirm'
|
|
1480
1547
|
| '/_nav/apps/anyhub'
|
|
1481
1548
|
| '/_nav/design/$projectId'
|
|
1482
1549
|
| '/_nav/knowledge/$kbId'
|
|
@@ -1510,6 +1577,8 @@ export interface FileRouteTypes {
|
|
|
1510
1577
|
| '/api/attachments/upload'
|
|
1511
1578
|
| '/api/auth/$'
|
|
1512
1579
|
| '/api/auth/desktop-callback'
|
|
1580
|
+
| '/api/cron/changelog-release'
|
|
1581
|
+
| '/api/cron/subscription-reminders'
|
|
1513
1582
|
| '/api/design/upload'
|
|
1514
1583
|
| '/api/knowledge/sync-github'
|
|
1515
1584
|
| '/api/notifications/stream'
|
|
@@ -1571,6 +1640,8 @@ export interface RootRouteChildren {
|
|
|
1571
1640
|
ApiAttachmentsUploadRoute: typeof ApiAttachmentsUploadRoute
|
|
1572
1641
|
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
|
|
1573
1642
|
ApiAuthDesktopCallbackRoute: typeof ApiAuthDesktopCallbackRoute
|
|
1643
|
+
ApiCronChangelogReleaseRoute: typeof ApiCronChangelogReleaseRoute
|
|
1644
|
+
ApiCronSubscriptionRemindersRoute: typeof ApiCronSubscriptionRemindersRoute
|
|
1574
1645
|
ApiDesignUploadRoute: typeof ApiDesignUploadRoute
|
|
1575
1646
|
ApiKnowledgeSyncGithubRoute: typeof ApiKnowledgeSyncGithubRoute
|
|
1576
1647
|
ApiNotificationsStreamRoute: typeof ApiNotificationsStreamRoute
|
|
@@ -1631,6 +1702,13 @@ declare module '@tanstack/react-router' {
|
|
|
1631
1702
|
preLoaderRoute: typeof _desktopOnboardingRouteRouteImport
|
|
1632
1703
|
parentRoute: typeof rootRouteImport
|
|
1633
1704
|
}
|
|
1705
|
+
'/_login/email-unsubscribe': {
|
|
1706
|
+
id: '/_login/email-unsubscribe'
|
|
1707
|
+
path: '/email-unsubscribe'
|
|
1708
|
+
fullPath: '/email-unsubscribe'
|
|
1709
|
+
preLoaderRoute: typeof LoginEmailUnsubscribeRouteRouteImport
|
|
1710
|
+
parentRoute: typeof LoginRouteRoute
|
|
1711
|
+
}
|
|
1634
1712
|
'/_login/forgot-password': {
|
|
1635
1713
|
id: '/_login/forgot-password'
|
|
1636
1714
|
path: '/forgot-password'
|
|
@@ -1659,6 +1737,13 @@ declare module '@tanstack/react-router' {
|
|
|
1659
1737
|
preLoaderRoute: typeof LoginResetPasswordRouteRouteImport
|
|
1660
1738
|
parentRoute: typeof LoginRouteRoute
|
|
1661
1739
|
}
|
|
1740
|
+
'/_login/verify-email': {
|
|
1741
|
+
id: '/_login/verify-email'
|
|
1742
|
+
path: '/verify-email'
|
|
1743
|
+
fullPath: '/verify-email'
|
|
1744
|
+
preLoaderRoute: typeof LoginVerifyEmailRouteRouteImport
|
|
1745
|
+
parentRoute: typeof LoginRouteRoute
|
|
1746
|
+
}
|
|
1662
1747
|
'/_nav/acceptance-criteria': {
|
|
1663
1748
|
id: '/_nav/acceptance-criteria'
|
|
1664
1749
|
path: '/acceptance-criteria'
|
|
@@ -1883,6 +1968,13 @@ declare module '@tanstack/react-router' {
|
|
|
1883
1968
|
preLoaderRoute: typeof LangDocsSplatRouteRouteImport
|
|
1884
1969
|
parentRoute: typeof rootRouteImport
|
|
1885
1970
|
}
|
|
1971
|
+
'/_login/changelog/confirm': {
|
|
1972
|
+
id: '/_login/changelog/confirm'
|
|
1973
|
+
path: '/changelog/confirm'
|
|
1974
|
+
fullPath: '/changelog/confirm'
|
|
1975
|
+
preLoaderRoute: typeof LoginChangelogConfirmRouteRouteImport
|
|
1976
|
+
parentRoute: typeof LoginRouteRoute
|
|
1977
|
+
}
|
|
1886
1978
|
'/_nav/apps/anyhub': {
|
|
1887
1979
|
id: '/_nav/apps/anyhub'
|
|
1888
1980
|
path: '/anyhub'
|
|
@@ -2121,6 +2213,20 @@ declare module '@tanstack/react-router' {
|
|
|
2121
2213
|
preLoaderRoute: typeof ApiAuthDesktopCallbackRouteImport
|
|
2122
2214
|
parentRoute: typeof rootRouteImport
|
|
2123
2215
|
}
|
|
2216
|
+
'/api/cron/changelog-release': {
|
|
2217
|
+
id: '/api/cron/changelog-release'
|
|
2218
|
+
path: '/api/cron/changelog-release'
|
|
2219
|
+
fullPath: '/api/cron/changelog-release'
|
|
2220
|
+
preLoaderRoute: typeof ApiCronChangelogReleaseRouteImport
|
|
2221
|
+
parentRoute: typeof rootRouteImport
|
|
2222
|
+
}
|
|
2223
|
+
'/api/cron/subscription-reminders': {
|
|
2224
|
+
id: '/api/cron/subscription-reminders'
|
|
2225
|
+
path: '/api/cron/subscription-reminders'
|
|
2226
|
+
fullPath: '/api/cron/subscription-reminders'
|
|
2227
|
+
preLoaderRoute: typeof ApiCronSubscriptionRemindersRouteImport
|
|
2228
|
+
parentRoute: typeof rootRouteImport
|
|
2229
|
+
}
|
|
2124
2230
|
'/api/design/upload': {
|
|
2125
2231
|
id: '/api/design/upload'
|
|
2126
2232
|
path: '/api/design/upload'
|
|
@@ -2377,17 +2483,23 @@ declare module '@tanstack/react-router' {
|
|
|
2377
2483
|
}
|
|
2378
2484
|
|
|
2379
2485
|
interface LoginRouteRouteChildren {
|
|
2486
|
+
LoginEmailUnsubscribeRouteRoute: typeof LoginEmailUnsubscribeRouteRoute
|
|
2380
2487
|
LoginForgotPasswordRouteRoute: typeof LoginForgotPasswordRouteRoute
|
|
2381
2488
|
LoginLoginRouteRoute: typeof LoginLoginRouteRoute
|
|
2382
2489
|
LoginRegisterRouteRoute: typeof LoginRegisterRouteRoute
|
|
2383
2490
|
LoginResetPasswordRouteRoute: typeof LoginResetPasswordRouteRoute
|
|
2491
|
+
LoginVerifyEmailRouteRoute: typeof LoginVerifyEmailRouteRoute
|
|
2492
|
+
LoginChangelogConfirmRouteRoute: typeof LoginChangelogConfirmRouteRoute
|
|
2384
2493
|
}
|
|
2385
2494
|
|
|
2386
2495
|
const LoginRouteRouteChildren: LoginRouteRouteChildren = {
|
|
2496
|
+
LoginEmailUnsubscribeRouteRoute: LoginEmailUnsubscribeRouteRoute,
|
|
2387
2497
|
LoginForgotPasswordRouteRoute: LoginForgotPasswordRouteRoute,
|
|
2388
2498
|
LoginLoginRouteRoute: LoginLoginRouteRoute,
|
|
2389
2499
|
LoginRegisterRouteRoute: LoginRegisterRouteRoute,
|
|
2390
2500
|
LoginResetPasswordRouteRoute: LoginResetPasswordRouteRoute,
|
|
2501
|
+
LoginVerifyEmailRouteRoute: LoginVerifyEmailRouteRoute,
|
|
2502
|
+
LoginChangelogConfirmRouteRoute: LoginChangelogConfirmRouteRoute,
|
|
2391
2503
|
}
|
|
2392
2504
|
|
|
2393
2505
|
const LoginRouteRouteWithChildren = LoginRouteRoute._addFileChildren(
|
|
@@ -2840,6 +2952,8 @@ const rootRouteChildren: RootRouteChildren = {
|
|
|
2840
2952
|
ApiAttachmentsUploadRoute: ApiAttachmentsUploadRoute,
|
|
2841
2953
|
ApiAuthSplatRoute: ApiAuthSplatRoute,
|
|
2842
2954
|
ApiAuthDesktopCallbackRoute: ApiAuthDesktopCallbackRoute,
|
|
2955
|
+
ApiCronChangelogReleaseRoute: ApiCronChangelogReleaseRoute,
|
|
2956
|
+
ApiCronSubscriptionRemindersRoute: ApiCronSubscriptionRemindersRoute,
|
|
2843
2957
|
ApiDesignUploadRoute: ApiDesignUploadRoute,
|
|
2844
2958
|
ApiKnowledgeSyncGithubRoute: ApiKnowledgeSyncGithubRoute,
|
|
2845
2959
|
ApiNotificationsStreamRoute: ApiNotificationsStreamRoute,
|
package/src/routes/__root.tsx
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { Link, Outlet, createRootRouteWithContext, HeadContent, Scripts, useParams } from '@tanstack/react-router'
|
|
19
|
+
import { useEffect } from 'react'
|
|
19
20
|
import { HotkeysProvider } from '@tanstack/react-hotkeys'
|
|
20
21
|
import { ShortcutsPanel } from '@/components/hotkeys/shortcuts-panel'
|
|
21
22
|
import { Analytics } from '@vercel/analytics/react'
|
|
@@ -53,7 +54,8 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|
|
53
54
|
{ name: 'description', content: '专为产品经理打造的协作平台' },
|
|
54
55
|
],
|
|
55
56
|
links: [
|
|
56
|
-
{ rel: 'icon', href: '/favicon.
|
|
57
|
+
{ rel: 'icon', href: '/favicon.svg', type: 'image/svg+xml' },
|
|
58
|
+
{ rel: 'icon', href: '/favicon.ico', sizes: '48x48' },
|
|
57
59
|
{ rel: 'stylesheet', href: '/fonts/fonts.css' },
|
|
58
60
|
{ rel: 'stylesheet', href: globalsCss },
|
|
59
61
|
],
|
|
@@ -66,6 +68,20 @@ export const Route = createRootRouteWithContext<RouterContext>()({
|
|
|
66
68
|
function RootComponent() {
|
|
67
69
|
const session = Route.useRouteContext({ select: (c) => c.session }) as Session | null
|
|
68
70
|
const { lang } = useParams({ strict: false }) as { lang?: string | undefined }
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const media = window.matchMedia('(prefers-color-scheme: dark)')
|
|
74
|
+
const update = () => {
|
|
75
|
+
const link = document.querySelector<HTMLLinkElement>('link[rel="icon"][type="image/svg+xml"]')
|
|
76
|
+
if (!link) return
|
|
77
|
+
const target = media.matches ? '/favicon-dark.svg' : '/favicon.svg'
|
|
78
|
+
if (link.getAttribute('href') !== target) link.href = target
|
|
79
|
+
}
|
|
80
|
+
update()
|
|
81
|
+
media.addEventListener('change', update)
|
|
82
|
+
return () => media.removeEventListener('change', update)
|
|
83
|
+
}, [])
|
|
84
|
+
|
|
69
85
|
return (
|
|
70
86
|
<html lang={getLocale()} suppressHydrationWarning>
|
|
71
87
|
<head>
|
|
@@ -83,8 +99,9 @@ function RootComponent() {
|
|
|
83
99
|
</RootProvider>
|
|
84
100
|
</Providers>
|
|
85
101
|
<Scripts />
|
|
86
|
-
|
|
87
|
-
<
|
|
102
|
+
{/* va.vercel-scripts.com 在部分网络环境不可达,dev 下跳过避免控制台噪音 */}
|
|
103
|
+
{import.meta.env.PROD && <Analytics />}
|
|
104
|
+
{import.meta.env.PROD && <SpeedInsights />}
|
|
88
105
|
</body>
|
|
89
106
|
</html>
|
|
90
107
|
)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { loginSchema, registerSchema, resetPasswordSchema, forgotPasswordSchema } from '../_login/schemas/-auth'
|
|
3
|
+
|
|
4
|
+
describe('registerSchema', () => {
|
|
5
|
+
it('拒绝纯数字密码(缺字母)', () => {
|
|
6
|
+
expect(registerSchema.safeParse({ username: 'alice', email: 'a@b.com', password: '12345678', confirmPassword: '12345678', invitationCode: 'ABC123' }).success).toBe(false)
|
|
7
|
+
})
|
|
8
|
+
it('拒绝纯字母密码(缺数字)', () => {
|
|
9
|
+
expect(registerSchema.safeParse({ username: 'alice', email: 'a@b.com', password: 'abcdefgh', confirmPassword: 'abcdefgh', invitationCode: 'ABC123' }).success).toBe(false)
|
|
10
|
+
})
|
|
11
|
+
it('拒绝非法用户名字符', () => {
|
|
12
|
+
expect(registerSchema.safeParse({ username: '阿里', email: 'a@b.com', password: 'abc12345', confirmPassword: 'abc12345', invitationCode: 'ABC123' }).success).toBe(false)
|
|
13
|
+
})
|
|
14
|
+
it('接受字母+数字密码与合法用户名', () => {
|
|
15
|
+
expect(registerSchema.safeParse({ username: 'alice_01', email: 'a@b.com', password: 'abc12345', confirmPassword: 'abc12345', invitationCode: 'ABC123' }).success).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
it('invitationCode 可空(open 模式前端忽略,服务端校验)', () => {
|
|
18
|
+
expect(registerSchema.safeParse({ username: 'alice', email: 'a@b.com', password: 'abc12345', confirmPassword: 'abc12345', invitationCode: '' }).success).toBe(true)
|
|
19
|
+
})
|
|
20
|
+
it('invitationCode 拒绝非法字符', () => {
|
|
21
|
+
const r = registerSchema.safeParse({ username: 'alice', email: 'a@b.com', password: 'abc12345', confirmPassword: 'abc12345', invitationCode: '!!!' })
|
|
22
|
+
expect(r.success).toBe(false)
|
|
23
|
+
if (!r.success) expect(r.error.issues[0].path).toEqual(['invitationCode'])
|
|
24
|
+
})
|
|
25
|
+
it('invitationCode 拒绝超长(>32 位)', () => {
|
|
26
|
+
expect(registerSchema.safeParse({ username: 'alice', email: 'a@b.com', password: 'abc12345', confirmPassword: 'abc12345', invitationCode: 'a'.repeat(33) }).success).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
it('invitationCode 缺省(未传字段)→ 默认空串通过', () => {
|
|
29
|
+
expect(registerSchema.safeParse({ username: 'alice', email: 'a@b.com', password: 'abc12345', confirmPassword: 'abc12345' }).success).toBe(true)
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('resetPasswordSchema', () => {
|
|
34
|
+
it('拒绝纯数字密码', () => {
|
|
35
|
+
expect(resetPasswordSchema.safeParse({ password: '12345678', confirmPassword: '12345678' }).success).toBe(false)
|
|
36
|
+
})
|
|
37
|
+
it('接受字母+数字密码', () => {
|
|
38
|
+
expect(resetPasswordSchema.safeParse({ password: 'abc12345', confirmPassword: 'abc12345' }).success).toBe(true)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('邮箱归一化(emailRule transform)', () => {
|
|
43
|
+
it('loginSchema 归一小写并去首尾空格', () => {
|
|
44
|
+
const r = loginSchema.safeParse({ email: ' A@B.COM ', password: 'x', remember: false })
|
|
45
|
+
expect(r.success).toBe(true)
|
|
46
|
+
if (r.success) expect(r.data.email).toBe('a@b.com')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('forgotPasswordSchema 同样归一', () => {
|
|
50
|
+
const r = forgotPasswordSchema.safeParse({ email: 'X@Y.ORG' })
|
|
51
|
+
expect(r.success).toBe(true)
|
|
52
|
+
if (r.success) expect(r.data.email).toBe('x@y.org')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('registerSchema 同样归一', () => {
|
|
56
|
+
const r = registerSchema.safeParse({
|
|
57
|
+
username: 'alice_01', email: ' M@N.IO ', password: 'abc12345', confirmPassword: 'abc12345',
|
|
58
|
+
})
|
|
59
|
+
expect(r.success).toBe(true)
|
|
60
|
+
if (r.success) expect(r.data.email).toBe('m@n.io')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
@@ -3,10 +3,11 @@ import React from 'react'
|
|
|
3
3
|
|
|
4
4
|
// edition.ts 从 @d plugins/modules.gen 读 profileId 并派生 isSaasEdition,
|
|
5
5
|
// mock 必须在 edition 被 import 之前生效,否则 isSaasEdition 被缓存为 false(oss)。
|
|
6
|
-
async function importIndexRoute(profileId: 'oss' | 'saas') {
|
|
6
|
+
async function importIndexRoute(profileId: 'oss' | 'saas', desktop = false) {
|
|
7
7
|
vi.doMock('@/paraglide/runtime', () => ({ getLocale: () => 'zh', overwriteGetLocale: () => {}, locales: ['zh', 'en'], baseLocale: 'zh' }))
|
|
8
8
|
vi.resetModules()
|
|
9
9
|
vi.doMock('@/plugins/modules.gen', () => ({ profileId }))
|
|
10
|
+
vi.doMock('@/lib/edition-desktop', () => ({ isDesktopEdition: () => desktop }))
|
|
10
11
|
return import('../_public/index/route')
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -55,6 +56,18 @@ describe('_public/index beforeLoad', () => {
|
|
|
55
56
|
const { Route } = await importIndexRoute('saas')
|
|
56
57
|
expect(catchRedirect(() => Route.options.beforeLoad!( {} as never))).toBeNull()
|
|
57
58
|
}, 20000)
|
|
59
|
+
|
|
60
|
+
it('desktop + SaaS:redirect 到 /dashboard(跳过官网)', async () => {
|
|
61
|
+
const { Route } = await importIndexRoute('saas', true)
|
|
62
|
+
const r = catchRedirect(() => Route.options.beforeLoad!( {} as never))
|
|
63
|
+
expect(r?.to).toBe('/dashboard')
|
|
64
|
+
}, 20000)
|
|
65
|
+
|
|
66
|
+
it('desktop + OSS:redirect 到 /dashboard', async () => {
|
|
67
|
+
const { Route } = await importIndexRoute('oss', true)
|
|
68
|
+
const r = catchRedirect(() => Route.options.beforeLoad!( {} as never))
|
|
69
|
+
expect(r?.to).toBe('/dashboard')
|
|
70
|
+
}, 20000)
|
|
58
71
|
})
|
|
59
72
|
|
|
60
73
|
describe('_nav beforeLoad', () => {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createLazyFileRoute, Link } from '@tanstack/react-router'
|
|
19
|
+
import { RainbowButton } from '@/components/magicui/rainbow-button'
|
|
20
|
+
import { ArrowRight } from 'lucide-react'
|
|
21
|
+
import { AuthLayout, AuthLayoutLeft, AuthLayoutRight } from '@/components/auth/auth-layout'
|
|
22
|
+
import { AuthHeader } from '@/components/auth/auth-header'
|
|
23
|
+
import { AuthBrandPanel } from '@/components/auth/auth-brand-panel'
|
|
24
|
+
import { m } from '@/paraglide/messages.js'
|
|
25
|
+
|
|
26
|
+
export const Route = createLazyFileRoute('/_login/changelog/confirm')({
|
|
27
|
+
component: ChangelogConfirmPage,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
function ChangelogConfirmPage() {
|
|
31
|
+
const { ok } = Route.useRouteContext()
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<AuthLayout>
|
|
35
|
+
<AuthLayoutLeft>
|
|
36
|
+
{ok ? (
|
|
37
|
+
<>
|
|
38
|
+
<AuthHeader
|
|
39
|
+
title={m.common_changelog_confirm_success()}
|
|
40
|
+
subtitle={m.common_changelog_confirm_successDesc()}
|
|
41
|
+
/>
|
|
42
|
+
<RainbowButton asChild className="w-full">
|
|
43
|
+
<Link to="/changelog">
|
|
44
|
+
{m.common_changelog_confirm_view()}
|
|
45
|
+
<ArrowRight className="ml-2 h-4 w-4" />
|
|
46
|
+
</Link>
|
|
47
|
+
</RainbowButton>
|
|
48
|
+
</>
|
|
49
|
+
) : (
|
|
50
|
+
<>
|
|
51
|
+
<AuthHeader
|
|
52
|
+
title={m.common_changelog_confirm_invalid()}
|
|
53
|
+
subtitle={m.common_changelog_confirm_invalidDesc()}
|
|
54
|
+
/>
|
|
55
|
+
<RainbowButton asChild className="w-full">
|
|
56
|
+
<Link to="/changelog">
|
|
57
|
+
{m.common_changelog_confirm_resubscribe()}
|
|
58
|
+
<ArrowRight className="ml-2 h-4 w-4" />
|
|
59
|
+
</Link>
|
|
60
|
+
</RainbowButton>
|
|
61
|
+
</>
|
|
62
|
+
)}
|
|
63
|
+
</AuthLayoutLeft>
|
|
64
|
+
<AuthLayoutRight>
|
|
65
|
+
<AuthBrandPanel
|
|
66
|
+
title="AllBlue"
|
|
67
|
+
subtitle={ok ? m.common_changelog_confirm_success() : m.common_changelog_confirm_invalid()}
|
|
68
|
+
/>
|
|
69
|
+
</AuthLayoutRight>
|
|
70
|
+
</AuthLayout>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// AllBlue - 理想之海
|
|
3
|
+
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
+
//
|
|
5
|
+
// This program is free software: you can redistribute it and/or modify
|
|
6
|
+
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
// (at your option) any later version.
|
|
9
|
+
//
|
|
10
|
+
// This program is distributed in the hope that it will be useful,
|
|
11
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
// GNU Affero General Public License for more details.
|
|
14
|
+
//
|
|
15
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
19
|
+
import { confirmChangelogSubscriptionFn } from '@/server/serverFns/changelog/subscribe'
|
|
20
|
+
|
|
21
|
+
export const Route = createFileRoute('/_login/changelog/confirm')({
|
|
22
|
+
validateSearch: (s: { token?: string }) => ({ token: s.token ?? '' }),
|
|
23
|
+
beforeLoad: async ({ search }) => {
|
|
24
|
+
if (!search.token) return { ok: false }
|
|
25
|
+
const r = await confirmChangelogSubscriptionFn({ data: { token: search.token } })
|
|
26
|
+
return { ok: r.ok }
|
|
27
|
+
},
|
|
28
|
+
})
|