From b3e7c2a318d54089b8db7a0bb84c7604bdf611b5 Mon Sep 17 00:00:00 2001 From: weizong song Date: Sun, 7 Jun 2026 09:34:44 +0800 Subject: [PATCH] fix: pass public source through signup navigation --- src/router/index.ts | 22 ++++++++++++++++------ src/utils/publicSource.ts | 15 ++++++++++++++- src/views/ParticipantLandingView.vue | 10 ++++++++-- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/router/index.ts b/src/router/index.ts index 86ebe09..30aa127 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -15,15 +15,21 @@ import './admin/routeMeta' const MainLayout = () => import('../layouts/MainLayout.vue') -function participantRootRedirect() { +function sourceQueryFrom(to: { query?: Record }): { src: string } | undefined { + const src = typeof to.query?.src === 'string' ? to.query.src.trim() : '' + return /^[A-Za-z0-9_-]{2,64}$/.test(src) ? { src } : undefined +} + +function participantRootRedirect(to?: { query?: Record }) { const t = localStorage.getItem(TOKEN_KEY) const s = localStorage.getItem(PARTICIPANT_COMPETITION_SLUG_KEY) + const query = to ? sourceQueryFrom(to) : undefined if (s) { return t - ? { name: 'participant-apply', params: { slug: s }, replace: true as const } - : { name: 'participant-login', params: { slug: s }, replace: true as const } + ? { name: 'participant-apply', params: { slug: s }, query, replace: true as const } + : { name: 'participant-login', params: { slug: s }, query, replace: true as const } } - return { path: '/c', replace: true as const } + return { path: '/c', query, replace: true as const } } const router = createRouter({ @@ -173,9 +179,13 @@ router.beforeEach(async (to) => { if (to.matched.some((r) => r.meta.requiresAuth) && !participantToken) { const slug = to.params.slug != null ? String(to.params.slug) : '' if (slug) { - return { name: 'participant-login', params: { slug }, query: { redirect: to.fullPath } } + return { + name: 'participant-login', + params: { slug }, + query: { redirect: to.fullPath, ...sourceQueryFrom(to) }, + } } - return { path: '/c', query: { redirect: to.fullPath } } + return { path: '/c', query: { redirect: to.fullPath, ...sourceQueryFrom(to) } } } if (to.meta.guestOnly && participantToken) { diff --git a/src/utils/publicSource.ts b/src/utils/publicSource.ts index 0121a6f..30d6748 100644 --- a/src/utils/publicSource.ts +++ b/src/utils/publicSource.ts @@ -11,6 +11,11 @@ interface ResolveResponse { source_name?: string } +function normalizeSourceCode(value: unknown): string | null { + const sourceCode = typeof value === 'string' ? value.trim() : '' + return /^[A-Za-z0-9_-]{2,64}$/.test(sourceCode) ? sourceCode : null +} + function nowMs(): number { return Date.now() } @@ -39,6 +44,14 @@ function writePublicSourceCache(sourceCode: string): void { } export function readPublicSourceCodeForAttribution(): string | null { + const currentUrlSourceCode = + typeof window !== 'undefined' + ? normalizeSourceCode(new URLSearchParams(window.location.search).get('src')) + : null + if (currentUrlSourceCode) { + return currentUrlSourceCode + } + if (!storageAvailable()) return null const sourceCode = window.localStorage.getItem(SOURCE_CODE_KEY)?.trim() ?? '' const checkedAtRaw = window.localStorage.getItem(SOURCE_CHECKED_AT_KEY)?.trim() ?? '' @@ -56,7 +69,7 @@ export function readPublicSourceCodeForAttribution(): string | null { async function resolvePublicSource(sourceCode: string): Promise { const trimmed = sourceCode.trim() - if (!/^[A-Za-z0-9_-]{2,64}$/.test(trimmed)) return null + if (!normalizeSourceCode(trimmed)) return null const qs = new URLSearchParams({ source_code: trimmed }) const res = await fetch(`${getApiBase()}/api/v1/public/source-channels/resolve?${qs.toString()}`, { diff --git a/src/views/ParticipantLandingView.vue b/src/views/ParticipantLandingView.vue index 8f83632..d0589e3 100644 --- a/src/views/ParticipantLandingView.vue +++ b/src/views/ParticipantLandingView.vue @@ -1,8 +1,9 @@