@allbluecn/web-app 0.10.3 → 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 +24 -11
- package/public/favicon-dark.svg +1 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +1 -0
- package/public/logo/allblue-logo-app-icon.svg +7 -0
- package/public/logo/allblue-logo-dark-square.svg +4 -0
- package/public/logo/allblue-logo-dark.png +0 -0
- package/public/logo/allblue-logo-dark.svg +4 -0
- package/public/logo/allblue-logo-light-square.svg +4 -0
- package/public/logo/allblue-logo-light.png +0 -0
- package/public/logo/allblue-logo-light.svg +4 -0
- package/src/__tests__/bench/baselines/format-timestamp.json +44 -0
- package/src/__tests__/bench/core-hot-path.bench.ts +71 -0
- package/src/__tests__/integration/db-crud.integration.test.ts +79 -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/__tests__/rejects-tothrow-regression.test.ts +31 -0
- package/src/__tests__/security/ai/prompt-injection.test.ts +105 -0
- package/src/__tests__/security/ssrf-url-whitelist.test.ts +54 -0
- package/src/__tests__/security/xss-sanitize.test.ts +99 -0
- package/src/__tests__/setup.ts +9 -0
- package/src/__tests__/visual/__screenshots__/theme-surface.vrt.test.tsx/theme-surface-dark-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/__screenshots__/theme-surface.vrt.test.tsx/theme-surface-light-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/__screenshots__/user-avatar.vrt.test.tsx/user-avatar-initial-fallback-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/theme-surface.vrt.test.tsx +54 -0
- package/src/__tests__/visual/user-avatar.vrt.test.tsx +32 -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/error/global-error-boundary.tsx +2 -2
- package/src/components/forms/app-field.tsx +3 -1
- package/src/components/knowledge/general-groups.ts +15 -0
- package/src/components/knowledge/kb-filter-popover.tsx +143 -0
- package/src/components/knowledge/knowledge-toolbar.tsx +17 -21
- package/src/components/knowledge-public/legal-compliance-editor.tsx +4 -2
- package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +9 -6
- package/src/components/layout/sidebar-03/app-sidebar.tsx +4 -15
- package/src/components/layout/sidebar-03/nav-knowledge.tsx +19 -17
- package/src/components/layout/sidebar-03/nav-workspace.tsx +20 -22
- package/src/components/notifications/settings-notifications.tsx +41 -1
- package/src/components/settings/personal-team/account/settings-profile.tsx +68 -1
- package/src/components/shared-kit/avatars/notion-avatar.tsx +2 -1
- package/src/components/shared-kit/icons/__tests__/lucide-curated-icons.test.ts +2 -2
- package/src/components/shared-kit/icons/__tests__/lucide-icon-picker.test.tsx +3 -2
- package/src/components/shared-kit/icons/lucide-icon-picker/curated-icons.ts +5 -10
- package/src/components/shared-kit/icons/lucide-icon-picker/lucide-icon-by-name.tsx +3 -3
- package/src/components/shared-kit/theme/theme-surface.tsx +52 -0
- package/src/components/story/relations/general-groups.ts +1 -15
- package/src/components/story/relations/knowledge-select-dialog.tsx +13 -87
- package/src/components/story/story-list-view.tsx +7 -1
- package/src/components/tiptap/node/table-node/ui/table-move-row-column-button/use-table-move-row-column.ts +0 -2
- package/src/components/workspace/__tests__/activity/workspace-activity-card.test.tsx +36 -0
- package/src/components/workspace/__tests__/contribution/workspace-contribution-card.test.tsx +17 -0
- package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +63 -0
- package/src/components/workspace/__tests__/dialogs/delete-workspace-options.test.tsx +89 -0
- package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +68 -0
- package/src/components/workspace/__tests__/header/workspace-header.test.tsx +40 -0
- package/src/components/workspace/__tests__/kb/workspace-kb-card.test.tsx +154 -0
- package/src/components/workspace/__tests__/members/workspace-member-item.test.tsx +24 -0
- package/src/components/workspace/__tests__/members/workspace-members-manager.test.tsx +51 -0
- package/src/components/workspace/__tests__/projects/workspace-project-list.test.tsx +46 -0
- package/src/components/workspace/{create-workspace-dialog.tsx → dialogs/create-workspace-dialog.tsx} +1 -1
- package/src/components/workspace/{edit-workspace-dialog.tsx → dialogs/edit-workspace-dialog.tsx} +1 -1
- package/src/components/workspace/kb/workspace-kb-card.tsx +594 -0
- package/src/components/workspace/kb/workspace-kb-manage-dialog.tsx +174 -0
- package/src/components/workspace/kb/workspace-quick-doc-dialog.tsx +431 -0
- package/src/components/workspace/{workspace-members-manager.tsx → members/workspace-members-manager.tsx} +78 -40
- package/src/lib/__tests__/edition-desktop.test.ts +23 -0
- package/src/lib/__tests__/msw-sample.test.ts +68 -0
- package/src/lib/__tests__/pgvector.test.ts +2 -2
- package/src/lib/app-version.ts +1 -1
- package/src/lib/auth/auth.config.ts +123 -99
- package/src/lib/changelog/sdk-versions.ts +21 -21
- package/src/lib/collections/knowledge/knowledge-bases-collection.ts +3 -1
- package/src/lib/collections/reference/workspaces-collection.ts +3 -1
- package/src/lib/edition-desktop.ts +22 -0
- package/src/lib/fetch-json.ts +23 -0
- package/src/{components/workspace/workspace-kb-card.tsx → lib/i18n/resolver.ts} +6 -12
- package/src/lib/sanitize.ts +14 -1
- package/src/lib/session.ts +10 -2
- package/src/lib/vector-store/__tests__/factory.test.ts +1 -1
- package/src/lib/vector-store/__tests__/sqlite-vec-store.test.ts +2 -2
- package/src/plugins/core/tags/__tests__/tags.test.ts +10 -10
- 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 +18 -5
- package/src/routes/__tests__/route-tree-structure.test.ts +66 -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/knowledge/compliance/index.lazy.tsx +5 -2
- package/src/routes/_nav/knowledge/index.lazy.tsx +1 -1
- package/src/routes/_nav/knowledge/laws/index.lazy.tsx +5 -2
- package/src/routes/_nav/route.tsx +26 -0
- package/src/routes/_nav/workspaces/$workspaceId/route.lazy.tsx +13 -11
- package/src/routes/_nav/workspaces/$workspaceId/route.tsx +4 -2
- package/src/routes/_nav/workspaces/$workspaceId/settings/route.lazy.tsx +1 -1
- package/src/routes/_nav/workspaces/index.lazy.tsx +3 -3
- 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__/agent-crud.test.ts +4 -4
- package/src/server/__tests__/auth-registration.test.ts +60 -0
- package/src/server/__tests__/chat-conversations.test.ts +1 -3
- package/src/server/__tests__/fetch-guard.test.ts +99 -0
- package/src/server/__tests__/permissions.test.ts +1 -3
- package/src/server/__tests__/resources.team-seats.test.ts +2 -0
- package/src/server/__tests__/skill-crud.test.ts +3 -3
- package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +95 -0
- package/src/server/ai-gateway/__tests__/kb-context-guard.test.ts +136 -0
- package/src/server/ai-gateway/__tests__/knowledge-search-fence.test.ts +240 -0
- package/src/server/ai-gateway/__tests__/llm-fence.test.ts +125 -0
- package/src/server/ai-gateway/__tests__/run-store.test.ts +52 -0
- package/src/server/ai-gateway/ai-output-guard.ts +77 -0
- package/src/server/ai-gateway/index.ts +5 -0
- package/src/server/ai-gateway/kb-context-guard.ts +46 -0
- package/src/server/ai-gateway/llm-fence.ts +82 -0
- package/src/server/ai-gateway/persistence/run-store.ts +19 -0
- package/src/server/ai-gateway/story-prompt.ts +4 -2
- package/src/server/ai-gateway/tools/definitions.ts +12 -6
- package/src/server/ai-gateway/tools/server.ts +97 -54
- package/src/server/auth-registration.ts +40 -0
- package/src/server/billing-email.ts +39 -0
- package/src/server/builtin-project.ts +21 -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 +36 -13
- 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/fetch-guard.ts +170 -0
- package/src/server/middlewares/security-headers.ts +122 -0
- 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__/billing.test.ts +263 -0
- package/src/server/serverFns/__tests__/email-verification.test.ts +149 -0
- package/src/server/serverFns/__tests__/link-preview.test.ts +239 -0
- package/src/server/serverFns/__tests__/project-kb.test.ts +3 -3
- package/src/server/serverFns/__tests__/project-team-assign.test.ts +1 -1
- package/src/server/serverFns/__tests__/story-comments.test.ts +265 -0
- package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +266 -0
- package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +227 -0
- package/src/server/serverFns/__tests__/story-crud.test.ts +526 -0
- package/src/server/serverFns/__tests__/story-links.test.ts +262 -0
- package/src/server/serverFns/__tests__/story-list-query.test.ts +259 -0
- package/src/server/serverFns/__tests__/story-reactions.test.ts +230 -0
- package/src/server/serverFns/__tests__/story-tree.test.ts +317 -0
- package/src/server/serverFns/__tests__/story-types.test.ts +294 -0
- package/src/server/serverFns/__tests__/team-accept.test.ts +6 -6
- package/src/server/serverFns/__tests__/team-invitation.test.ts +5 -5
- package/src/server/serverFns/__tests__/team-join-request.test.ts +8 -8
- package/src/server/serverFns/__tests__/update-seats.test.ts +4 -4
- package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +160 -0
- package/src/server/serverFns/__tests__/workspace-kb-utils.test.ts +54 -0
- package/src/server/serverFns/__tests__/workspace-kb.test.ts +348 -0
- package/src/server/serverFns/__tests__/workspace.test.ts +28 -8
- 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/iteration/__tests__/iteration-crud.test.ts +3 -5
- package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +1 -1
- package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +2 -2
- package/src/server/serverFns/kb-doc-search.ts +111 -0
- package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
- package/src/server/serverFns/notifications.ts +10 -0
- package/src/server/serverFns/story/__tests__/bulk.test.ts +1 -1
- package/src/server/serverFns/story/__tests__/label-suggest-fn.test.ts +2 -6
- package/src/server/serverFns/story/__tests__/story-assoc.test.ts +2 -4
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +2 -2
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +2 -2
- package/src/server/serverFns/story/kb-general-consts.ts +39 -0
- package/src/server/serverFns/story/kb-general.ts +30 -17
- package/src/server/serverFns/story/story-assoc.ts +13 -103
- package/src/server/serverFns/story/story-crud.ts +2 -5
- package/src/server/serverFns/story/story-groups.ts +4 -13
- package/src/server/serverFns/team.ts +50 -12
- package/src/server/serverFns/workspace-kb-utils.ts +138 -0
- package/src/server/serverFns/workspace-kb.ts +403 -0
- package/src/server/serverFns/workspace.ts +4 -1
- package/src/shared/ai-cli/__tests__/adapter.test.ts +67 -0
- package/src/shared/ai-cli/__tests__/cli-detector.test.ts +99 -0
- package/src/shared/ai-cli/adapter.ts +2 -2
- package/src/shared/ai-cli/cli-detector.ts +18 -9
- package/src/start.ts +2 -1
- package/src/styles/globals.css +114 -0
- package/src/types/jest-dom-vitest.d.ts +8 -7
- package/vite.shared.ts +105 -4
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/window.svg +0 -1
- package/src/lib/__tests__/cli-detector.test.ts +0 -80
- /package/src/components/workspace/{workspace-activity-card.tsx → activity/workspace-activity-card.tsx} +0 -0
- /package/src/components/workspace/{workspace-contribution-card.tsx → contribution/workspace-contribution-card.tsx} +0 -0
- /package/src/components/workspace/{delete-workspace-options.tsx → dialogs/delete-workspace-options.tsx} +0 -0
- /package/src/components/workspace/{workspace-header.tsx → header/workspace-header.tsx} +0 -0
- /package/src/components/workspace/{workspace-member-item.tsx → members/workspace-member-item.tsx} +0 -0
- /package/src/components/workspace/{workspace-project-list.tsx → projects/workspace-project-list.tsx} +0 -0
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
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, Settings } 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/email-unsubscribe')({
|
|
27
|
+
component: EmailUnsubscribePage,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
function EmailUnsubscribePage() {
|
|
31
|
+
const { ok } = Route.useRouteContext()
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<AuthLayout>
|
|
35
|
+
<AuthLayoutLeft>
|
|
36
|
+
{ok ? (
|
|
37
|
+
<>
|
|
38
|
+
<AuthHeader
|
|
39
|
+
title={m.common_email_unsubscribe_success()}
|
|
40
|
+
subtitle={m.common_email_unsubscribe_successDesc()}
|
|
41
|
+
/>
|
|
42
|
+
<RainbowButton asChild className="w-full">
|
|
43
|
+
<Link to="/settings/notifications">
|
|
44
|
+
<Settings className="mr-2 h-4 w-4" />
|
|
45
|
+
{m.common_email_unsubscribe_manage()}
|
|
46
|
+
<ArrowRight className="ml-2 h-4 w-4" />
|
|
47
|
+
</Link>
|
|
48
|
+
</RainbowButton>
|
|
49
|
+
<div className="mt-4 text-center text-sm text-muted-foreground">
|
|
50
|
+
{m.common_email_unsubscribe_transactionalNote()}
|
|
51
|
+
</div>
|
|
52
|
+
</>
|
|
53
|
+
) : (
|
|
54
|
+
<>
|
|
55
|
+
<AuthHeader
|
|
56
|
+
title={m.common_email_unsubscribe_invalid()}
|
|
57
|
+
subtitle={m.common_email_unsubscribe_invalidDesc()}
|
|
58
|
+
/>
|
|
59
|
+
<RainbowButton asChild className="w-full">
|
|
60
|
+
<Link to="/login">
|
|
61
|
+
{m.common_email_unsubscribe_login()}
|
|
62
|
+
<ArrowRight className="ml-2 h-4 w-4" />
|
|
63
|
+
</Link>
|
|
64
|
+
</RainbowButton>
|
|
65
|
+
</>
|
|
66
|
+
)}
|
|
67
|
+
</AuthLayoutLeft>
|
|
68
|
+
<AuthLayoutRight>
|
|
69
|
+
<AuthBrandPanel
|
|
70
|
+
title="AllBlue"
|
|
71
|
+
subtitle={ok ? m.common_email_unsubscribe_success() : m.common_email_unsubscribe_invalid()}
|
|
72
|
+
/>
|
|
73
|
+
</AuthLayoutRight>
|
|
74
|
+
</AuthLayout>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
@@ -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 { unsubscribeByEmailTokenFn } from '@/server/serverFns/email-unsubscribe'
|
|
20
|
+
|
|
21
|
+
export const Route = createFileRoute('/_login/email-unsubscribe')({
|
|
22
|
+
validateSearch: (s: { token?: string }) => ({ token: s.token ?? '' }),
|
|
23
|
+
beforeLoad: async ({ search }) => {
|
|
24
|
+
if (!search.token) return { ok: false }
|
|
25
|
+
const r = await unsubscribeByEmailTokenFn({ data: { token: search.token } })
|
|
26
|
+
return { ok: r.ok }
|
|
27
|
+
},
|
|
28
|
+
})
|
|
@@ -62,7 +62,7 @@ function ForgotPasswordPage() {
|
|
|
62
62
|
onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); form.handleSubmit() }}
|
|
63
63
|
className="space-y-6"
|
|
64
64
|
>
|
|
65
|
-
<AppField name="email" form={form}>
|
|
65
|
+
<AppField name="email" form={form} renderError={false}>
|
|
66
66
|
{(field) => (
|
|
67
67
|
<IconInput
|
|
68
68
|
icon={Mail}
|
|
@@ -71,6 +71,7 @@ function ForgotPasswordPage() {
|
|
|
71
71
|
value={field.value}
|
|
72
72
|
onBlur={field.handleBlur}
|
|
73
73
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
74
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
74
75
|
/>
|
|
75
76
|
)}
|
|
76
77
|
</AppField>
|
|
@@ -166,7 +166,7 @@ function LoginPage() {
|
|
|
166
166
|
onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); form.handleSubmit() }}
|
|
167
167
|
className="space-y-6"
|
|
168
168
|
>
|
|
169
|
-
<AppField name="email" form={form}>
|
|
169
|
+
<AppField name="email" form={form} renderError={false}>
|
|
170
170
|
{(field) => (
|
|
171
171
|
<IconInput
|
|
172
172
|
icon={Mail}
|
|
@@ -175,6 +175,7 @@ function LoginPage() {
|
|
|
175
175
|
value={field.value}
|
|
176
176
|
onBlur={field.handleBlur}
|
|
177
177
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
178
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
178
179
|
/>
|
|
179
180
|
)}
|
|
180
181
|
</AppField>
|
|
@@ -184,7 +185,7 @@ function LoginPage() {
|
|
|
184
185
|
<SsoSection email={email} />
|
|
185
186
|
)}
|
|
186
187
|
/>
|
|
187
|
-
<AppField name="password" form={form}>
|
|
188
|
+
<AppField name="password" form={form} renderError={false}>
|
|
188
189
|
{(field) => (
|
|
189
190
|
<PasswordInput
|
|
190
191
|
label={m.auth_login_passwordLabel()}
|
|
@@ -192,6 +193,7 @@ function LoginPage() {
|
|
|
192
193
|
value={field.value}
|
|
193
194
|
onBlur={field.handleBlur}
|
|
194
195
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
196
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
195
197
|
/>
|
|
196
198
|
)}
|
|
197
199
|
</AppField>
|
|
@@ -39,7 +39,7 @@ export const Route = createLazyFileRoute('/_login/register')({
|
|
|
39
39
|
|
|
40
40
|
function RegisterPage() {
|
|
41
41
|
const router = useRouter()
|
|
42
|
-
const { googleEnabled } = Route.useLoaderData()
|
|
42
|
+
const { googleEnabled, registrationMode } = Route.useLoaderData()
|
|
43
43
|
const { invite, email: emailFromUrl } = Route.useSearch()
|
|
44
44
|
const registerFeatures = [
|
|
45
45
|
{ icon: Users, label: m.auth_features_userResearch(), desc: m.auth_features_userResearchDesc() },
|
|
@@ -58,6 +58,10 @@ function RegisterPage() {
|
|
|
58
58
|
},
|
|
59
59
|
validators: [{ run: registerSchema as any, triggers: ['change'] }],
|
|
60
60
|
onSubmit: async ({ value }) => {
|
|
61
|
+
if (registrationMode === 'invite' && !value.invitationCode?.trim()) {
|
|
62
|
+
toast.error(m.common_form_invitationCodeRequired())
|
|
63
|
+
return
|
|
64
|
+
}
|
|
61
65
|
try {
|
|
62
66
|
await registerFn({ data: value })
|
|
63
67
|
toast.success(m.auth_register_success())
|
|
@@ -86,7 +90,7 @@ function RegisterPage() {
|
|
|
86
90
|
onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); form.handleSubmit() }}
|
|
87
91
|
className="space-y-6"
|
|
88
92
|
>
|
|
89
|
-
<AppField name="username" form={form}>
|
|
93
|
+
<AppField name="username" form={form} renderError={false}>
|
|
90
94
|
{(field) => (
|
|
91
95
|
<IconInput
|
|
92
96
|
icon={User}
|
|
@@ -95,10 +99,11 @@ function RegisterPage() {
|
|
|
95
99
|
value={field.value}
|
|
96
100
|
onBlur={field.handleBlur}
|
|
97
101
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
102
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
98
103
|
/>
|
|
99
104
|
)}
|
|
100
105
|
</AppField>
|
|
101
|
-
<AppField name="email" form={form}>
|
|
106
|
+
<AppField name="email" form={form} renderError={false}>
|
|
102
107
|
{(field) => (
|
|
103
108
|
<IconInput
|
|
104
109
|
icon={Mail}
|
|
@@ -107,11 +112,12 @@ function RegisterPage() {
|
|
|
107
112
|
value={field.value}
|
|
108
113
|
onBlur={field.handleBlur}
|
|
109
114
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
115
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
110
116
|
/>
|
|
111
117
|
)}
|
|
112
118
|
</AppField>
|
|
113
119
|
<div className="grid grid-cols-2 gap-4">
|
|
114
|
-
<AppField name="password" form={form}>
|
|
120
|
+
<AppField name="password" form={form} renderError={false}>
|
|
115
121
|
{(field) => (
|
|
116
122
|
<PasswordInput
|
|
117
123
|
label={m.auth_register_passwordLabel()}
|
|
@@ -119,10 +125,11 @@ function RegisterPage() {
|
|
|
119
125
|
value={field.value}
|
|
120
126
|
onBlur={field.handleBlur}
|
|
121
127
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
128
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
122
129
|
/>
|
|
123
130
|
)}
|
|
124
131
|
</AppField>
|
|
125
|
-
<AppField name="confirmPassword" form={form}>
|
|
132
|
+
<AppField name="confirmPassword" form={form} renderError={false}>
|
|
126
133
|
{(field) => (
|
|
127
134
|
<PasswordInput
|
|
128
135
|
label={m.auth_register_confirmPasswordLabel()}
|
|
@@ -130,22 +137,26 @@ function RegisterPage() {
|
|
|
130
137
|
value={field.value}
|
|
131
138
|
onBlur={field.handleBlur}
|
|
132
139
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
140
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
133
141
|
/>
|
|
134
142
|
)}
|
|
135
143
|
</AppField>
|
|
136
144
|
</div>
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
{registrationMode === 'invite' && (
|
|
146
|
+
<AppField name="invitationCode" form={form} renderError={false}>
|
|
147
|
+
{(field) => (
|
|
148
|
+
<IconInput
|
|
149
|
+
icon={QrCode}
|
|
150
|
+
label={m.auth_register_invitationCodeLabel()}
|
|
151
|
+
placeholder={m.auth_register_invitationCodePlaceholder()}
|
|
152
|
+
value={field.value}
|
|
153
|
+
onBlur={field.handleBlur}
|
|
154
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
155
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
</AppField>
|
|
159
|
+
)}
|
|
149
160
|
<form.Subscribe
|
|
150
161
|
selector={(state) => state.isSubmitting}
|
|
151
162
|
children={(isSubmitting) => (
|
|
@@ -18,11 +18,18 @@
|
|
|
18
18
|
import { createFileRoute } from '@tanstack/react-router'
|
|
19
19
|
import { z } from 'zod'
|
|
20
20
|
import { getAuthProviders } from '@/server/serverFns/auth/providers'
|
|
21
|
+
import { getRegistrationModeFn } from '@/server/serverFns/auth/registration-mode'
|
|
21
22
|
|
|
22
23
|
export const Route = createFileRoute('/_login/register')({
|
|
23
24
|
validateSearch: z.object({
|
|
24
25
|
invite: z.string().optional(),
|
|
25
26
|
email: z.string().optional(),
|
|
26
27
|
}),
|
|
27
|
-
loader: () =>
|
|
28
|
+
loader: async () => {
|
|
29
|
+
const [providers, { mode: registrationMode }] = await Promise.all([
|
|
30
|
+
getAuthProviders(),
|
|
31
|
+
getRegistrationModeFn(),
|
|
32
|
+
])
|
|
33
|
+
return { ...providers, registrationMode }
|
|
34
|
+
},
|
|
28
35
|
})
|
|
@@ -66,7 +66,7 @@ function ResetPasswordPage() {
|
|
|
66
66
|
className="space-y-6"
|
|
67
67
|
>
|
|
68
68
|
<div className="grid grid-cols-2 gap-4">
|
|
69
|
-
<AppField name="password" form={form}>
|
|
69
|
+
<AppField name="password" form={form} renderError={false}>
|
|
70
70
|
{(field) => (
|
|
71
71
|
<PasswordInput
|
|
72
72
|
label={m.auth_resetPassword_newPasswordLabel()}
|
|
@@ -74,10 +74,11 @@ function ResetPasswordPage() {
|
|
|
74
74
|
value={field.value}
|
|
75
75
|
onBlur={field.handleBlur}
|
|
76
76
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
77
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
77
78
|
/>
|
|
78
79
|
)}
|
|
79
80
|
</AppField>
|
|
80
|
-
<AppField name="confirmPassword" form={form}>
|
|
81
|
+
<AppField name="confirmPassword" form={form} renderError={false}>
|
|
81
82
|
{(field) => (
|
|
82
83
|
<PasswordInput
|
|
83
84
|
label={m.auth_resetPassword_confirmPasswordLabel()}
|
|
@@ -85,6 +86,7 @@ function ResetPasswordPage() {
|
|
|
85
86
|
value={field.value}
|
|
86
87
|
onBlur={field.handleBlur}
|
|
87
88
|
onChange={(e) => field.handleChange(e.target.value)}
|
|
89
|
+
error={field.meta.isTouched && field.errors.length > 0 ? (field.errors.map((e) => e.message).filter(Boolean)[0] as string | undefined) : undefined}
|
|
88
90
|
/>
|
|
89
91
|
)}
|
|
90
92
|
</AppField>
|
|
@@ -18,29 +18,52 @@
|
|
|
18
18
|
import { z } from "zod"
|
|
19
19
|
import { m } from '@/paraglide/messages.js'
|
|
20
20
|
|
|
21
|
+
const passwordRule = z
|
|
22
|
+
.string()
|
|
23
|
+
.min(8, m.common_form_passwordMin8())
|
|
24
|
+
.max(32, m.common_form_passwordMax32())
|
|
25
|
+
.regex(/[a-zA-Z]/, m.common_form_passwordNeedLetter())
|
|
26
|
+
.regex(/[0-9]/, m.common_form_passwordNeedDigit())
|
|
27
|
+
|
|
28
|
+
const usernameRule = z
|
|
29
|
+
.string()
|
|
30
|
+
.regex(/^[a-zA-Z0-9_-]{3,20}$/, m.common_form_usernameCharset())
|
|
31
|
+
|
|
32
|
+
const invitationCodeRule = z
|
|
33
|
+
.string()
|
|
34
|
+
.refine((v) => v === "" || /^[A-Za-z0-9]{3,32}$/.test(v), {
|
|
35
|
+
message: m.common_form_invitationCodeFormat(),
|
|
36
|
+
})
|
|
37
|
+
.default("")
|
|
38
|
+
|
|
39
|
+
const emailRule = z
|
|
40
|
+
.string()
|
|
41
|
+
.transform((v) => v.toLowerCase().trim())
|
|
42
|
+
.pipe(z.string().email(m.common_form_invalidEmail()))
|
|
43
|
+
|
|
21
44
|
export const loginSchema = z.object({
|
|
22
|
-
email:
|
|
45
|
+
email: emailRule,
|
|
23
46
|
password: z.string().min(1, m.common_form_passwordNotEmpty()),
|
|
24
47
|
remember: z.boolean(),
|
|
25
48
|
})
|
|
26
49
|
|
|
27
50
|
export const registerSchema = z.object({
|
|
28
|
-
username:
|
|
29
|
-
email:
|
|
30
|
-
password:
|
|
51
|
+
username: usernameRule,
|
|
52
|
+
email: emailRule,
|
|
53
|
+
password: passwordRule,
|
|
31
54
|
confirmPassword: z.string(),
|
|
32
|
-
invitationCode:
|
|
55
|
+
invitationCode: invitationCodeRule,
|
|
33
56
|
}).refine((data) => data.password === data.confirmPassword, {
|
|
34
57
|
message: m.common_form_passwordMismatch(),
|
|
35
58
|
path: ["confirmPassword"],
|
|
36
59
|
})
|
|
37
60
|
|
|
38
61
|
export const forgotPasswordSchema = z.object({
|
|
39
|
-
email:
|
|
62
|
+
email: emailRule,
|
|
40
63
|
})
|
|
41
64
|
|
|
42
65
|
export const resetPasswordSchema = z.object({
|
|
43
|
-
password:
|
|
66
|
+
password: passwordRule,
|
|
44
67
|
confirmPassword: z.string(),
|
|
45
68
|
}).refine((d) => d.password === d.confirmPassword, {
|
|
46
69
|
message: m.common_form_passwordMismatch(),
|
|
@@ -0,0 +1,82 @@
|
|
|
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 { toast } from '@/components/ui/sonner'
|
|
20
|
+
import { RainbowButton } from '@/components/magicui/rainbow-button'
|
|
21
|
+
import { ArrowRight } from 'lucide-react'
|
|
22
|
+
import { AuthLayout, AuthLayoutLeft, AuthLayoutRight } from '@/components/auth/auth-layout'
|
|
23
|
+
import { AuthHeader } from '@/components/auth/auth-header'
|
|
24
|
+
import { AuthBrandPanel } from '@/components/auth/auth-brand-panel'
|
|
25
|
+
import { resendVerificationEmailFn } from '@/server/serverFns/auth/email-verification'
|
|
26
|
+
import { m } from '@/paraglide/messages.js'
|
|
27
|
+
|
|
28
|
+
export const Route = createLazyFileRoute('/_login/verify-email')({
|
|
29
|
+
component: VerifyEmailPage,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
function VerifyEmailPage() {
|
|
33
|
+
const { verified } = Route.useRouteContext()
|
|
34
|
+
|
|
35
|
+
const handleResend = async () => {
|
|
36
|
+
try {
|
|
37
|
+
const r = await resendVerificationEmailFn({ data: {} })
|
|
38
|
+
toast.success(
|
|
39
|
+
r.alreadyVerified ? m.auth_verify_alreadyVerified() : m.auth_verify_resendSuccess(),
|
|
40
|
+
)
|
|
41
|
+
} catch (err) {
|
|
42
|
+
toast.error(err instanceof Error ? err.message : String(err))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<AuthLayout>
|
|
48
|
+
<AuthLayoutLeft>
|
|
49
|
+
{verified ? (
|
|
50
|
+
<>
|
|
51
|
+
<AuthHeader title={m.auth_verify_success()} subtitle={m.auth_verify_successDesc()} />
|
|
52
|
+
<RainbowButton asChild className="w-full">
|
|
53
|
+
<Link to="/dashboard">
|
|
54
|
+
{m.auth_verify_goDashboard()}
|
|
55
|
+
<ArrowRight className="ml-2 h-4 w-4" />
|
|
56
|
+
</Link>
|
|
57
|
+
</RainbowButton>
|
|
58
|
+
</>
|
|
59
|
+
) : (
|
|
60
|
+
<>
|
|
61
|
+
<AuthHeader title={m.auth_verify_invalid()} subtitle={m.auth_verify_invalidDesc()} />
|
|
62
|
+
<RainbowButton className="w-full" onClick={handleResend}>
|
|
63
|
+
{m.auth_verify_resend()}
|
|
64
|
+
</RainbowButton>
|
|
65
|
+
<div className="mt-4 flex items-center justify-center gap-2 text-sm text-muted-foreground">
|
|
66
|
+
<span>{m.auth_verify_notLogged()}</span>
|
|
67
|
+
<Link to="/login" className="text-primary hover:underline">
|
|
68
|
+
{m.auth_login_submit()}
|
|
69
|
+
</Link>
|
|
70
|
+
</div>
|
|
71
|
+
</>
|
|
72
|
+
)}
|
|
73
|
+
</AuthLayoutLeft>
|
|
74
|
+
<AuthLayoutRight>
|
|
75
|
+
<AuthBrandPanel
|
|
76
|
+
title="AllBlue"
|
|
77
|
+
subtitle={verified ? m.auth_verify_success() : m.auth_verify_invalid()}
|
|
78
|
+
/>
|
|
79
|
+
</AuthLayoutRight>
|
|
80
|
+
</AuthLayout>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
@@ -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 { confirmEmailVerificationFn } from '@/server/serverFns/auth/email-verification'
|
|
20
|
+
|
|
21
|
+
export const Route = createFileRoute('/_login/verify-email')({
|
|
22
|
+
validateSearch: (s: { token?: string }) => ({ token: s.token ?? '' }),
|
|
23
|
+
beforeLoad: async ({ search }) => {
|
|
24
|
+
if (!search.token) return { verified: false }
|
|
25
|
+
const r = await confirmEmailVerificationFn({ data: { token: search.token } })
|
|
26
|
+
return { verified: r.ok }
|
|
27
|
+
},
|
|
28
|
+
})
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { cn } from "@/lib/utils"
|
|
19
19
|
import { createLazyFileRoute, useNavigate } from '@tanstack/react-router'
|
|
20
20
|
import { useState } from 'react'
|
|
21
|
-
import { useQuery, keepPreviousData } from '@tanstack/react-query'
|
|
21
|
+
import { useQuery, keepPreviousData, useQueryClient } from '@tanstack/react-query'
|
|
22
22
|
import { listComplianceFn } from '@/server/serverFns/knowledge-public/compliance/list'
|
|
23
23
|
import { createComplianceFn } from '@/server/serverFns/knowledge-public/compliance/create'
|
|
24
24
|
import { getEntityTagsFn } from '@/server/serverFns/knowledge-public/entity-tags'
|
|
@@ -50,6 +50,7 @@ export const Route = createLazyFileRoute('/_nav/knowledge/compliance/')({ compon
|
|
|
50
50
|
|
|
51
51
|
function CompliancePage() {
|
|
52
52
|
const navigate = useNavigate()
|
|
53
|
+
const qc = useQueryClient()
|
|
53
54
|
const industryOptions = buildIndustryOptions()
|
|
54
55
|
const industryGroups = [
|
|
55
56
|
{ key: 'industry', label: m.knowledge_compliance_index_industry(), options: industryOptions },
|
|
@@ -94,7 +95,9 @@ function CompliancePage() {
|
|
|
94
95
|
search={search}
|
|
95
96
|
onSearchChange={setSearch}
|
|
96
97
|
onCreate={async () => {
|
|
97
|
-
const r = await createComplianceFn({ data: { title:
|
|
98
|
+
const r = await createComplianceFn({ data: { title: '新合规', industry: '', tags: [] } })
|
|
99
|
+
qc.invalidateQueries({ queryKey: ['compliance'] })
|
|
100
|
+
await qc.refetchQueries({ queryKey: ['compliance'], type: 'all' })
|
|
98
101
|
navigate({ to: '/knowledge/compliance/edit/$id', params: { id: r.id } })
|
|
99
102
|
}}
|
|
100
103
|
createLabel={m.knowledge_compliance_index_add()}
|
|
@@ -146,7 +146,7 @@ function KnowledgeListPage() {
|
|
|
146
146
|
<Card key={kb.id} className={`group relative p-5 transition-colors hover:bg-accent/40 border rounded-lg ${isLaw ? 'overflow-hidden' : ''}`}>
|
|
147
147
|
<Link
|
|
148
148
|
to={kb.link}
|
|
149
|
-
className="absolute inset-0"
|
|
149
|
+
className="absolute inset-0 z-10"
|
|
150
150
|
aria-label={name}
|
|
151
151
|
/>
|
|
152
152
|
{isLaw ? (
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { cn } from "@/lib/utils"
|
|
19
19
|
import { createLazyFileRoute, useNavigate } from '@tanstack/react-router'
|
|
20
20
|
import { useState } from 'react'
|
|
21
|
-
import { useQuery, keepPreviousData } from '@tanstack/react-query'
|
|
21
|
+
import { useQuery, keepPreviousData, useQueryClient } from '@tanstack/react-query'
|
|
22
22
|
import { listLegalFn } from '@/server/serverFns/knowledge-public/legal/list'
|
|
23
23
|
import { createLegalFn } from '@/server/serverFns/knowledge-public/legal/create'
|
|
24
24
|
import { getEntityTagsFn } from '@/server/serverFns/knowledge-public/entity-tags'
|
|
@@ -44,6 +44,7 @@ export const Route = createLazyFileRoute('/_nav/knowledge/laws/')({ component: L
|
|
|
44
44
|
|
|
45
45
|
function LawPage() {
|
|
46
46
|
const navigate = useNavigate()
|
|
47
|
+
const qc = useQueryClient()
|
|
47
48
|
const jurisdictionOptions = buildJurisdictionOptions()
|
|
48
49
|
const jurisdictionGroups = [
|
|
49
50
|
{ key: 'jurisdiction', label: m.knowledge_laws_index_jurisdiction(), options: jurisdictionOptions },
|
|
@@ -79,7 +80,9 @@ function LawPage() {
|
|
|
79
80
|
search={search}
|
|
80
81
|
onSearchChange={setSearch}
|
|
81
82
|
onCreate={async () => {
|
|
82
|
-
const r = await createLegalFn({ data: { title:
|
|
83
|
+
const r = await createLegalFn({ data: { title: '新法规', jurisdiction: '', category: '', tags: [] } })
|
|
84
|
+
qc.invalidateQueries({ queryKey: ['legal'] })
|
|
85
|
+
await qc.refetchQueries({ queryKey: ['legal'], type: 'all' })
|
|
83
86
|
navigate({ to: '/knowledge/laws/edit/$id', params: { id: r.id } })
|
|
84
87
|
}}
|
|
85
88
|
createLabel={m.knowledge_laws_index_add()}
|