22
33import { useEffect , useRef , useState , useCallback } from "react" ;
44import Link from "next/link" ;
5- import { useParams , useSearchParams } from "next/navigation" ;
5+ import { useParams , useSearchParams , useRouter } from "next/navigation" ;
66import { Api , HttpError } from "@/lib/api" ;
77import type { ClaimResponse , ClaimStatus } from "@/lib/types" ;
8- import { getClaimPassword , setClaimPassword } from "@/lib/auth" ;
8+ import { clearClaimPassword , getClaimPassword , setClaimPassword } from "@/lib/auth" ;
99import { useI18n } from "@/lib/i18n" ;
1010import { ClaimFlowchart } from "@/components/ClaimFlowchart" ;
1111
1212export default function ClientPage ( ) {
1313 const params = useParams < { id ?: string } > ( ) ;
1414 const searchParams = useSearchParams ( ) ;
15- const id = ( params ?. id as string ) || searchParams ?. get ( "id" ) || "" ;
15+ const paramsId = params ?. id as string | undefined ;
16+ const queryId = searchParams ?. get ( "id" ) || undefined ;
17+ const origin = searchParams ?. get ( "origin" ) || undefined ;
18+ const id = paramsId || queryId || "" ;
19+ const isLegacyViewMode = ! paramsId && ! ! queryId ;
20+ const isEntryFlow = origin === "entry" || isLegacyViewMode ;
21+ const router = useRouter ( ) ;
1622 const { t } = useI18n ( ) ;
1723
1824 const [ claim , setClaim ] = useState < ClaimResponse | null > ( null ) ;
@@ -25,6 +31,8 @@ export default function ClientPage() {
2531 type DownUrl = { url : string ; expiresAt ?: string } ;
2632 const [ downUrls , setDownUrls ] = useState < Record < string , DownUrl > > ( { } ) ;
2733 const [ updating , setUpdating ] = useState ( false ) ;
34+ const passwordSubmittedRef = useRef ( false ) ;
35+ const pendingPasswordRef = useRef < string | undefined > ( undefined ) ;
2836
2937 useEffect ( ( ) => {
3038 if ( ! id ) return ;
@@ -35,14 +43,19 @@ export default function ClientPage() {
3543
3644 const lastLoadIdRef = useRef ( 0 ) ;
3745 const load = useCallback ( async ( ) => {
38- if ( ! id || ! passwordChecked ) return ;
46+ if ( ! id || ! passwordChecked || askPassword ) return ;
3947 const thisLoadId = ++ lastLoadIdRef . current ;
4048 setLoading ( true ) ;
4149 setError ( undefined ) ;
4250 try {
4351 const c = await Api . getClaim ( id , effectivePassword || undefined ) ;
4452 if ( lastLoadIdRef . current !== thisLoadId ) return ;
4553 setClaim ( c ) ;
54+ passwordSubmittedRef . current = false ;
55+ if ( pendingPasswordRef . current !== undefined ) {
56+ setClaimPassword ( id , pendingPasswordRef . current ) ;
57+ pendingPasswordRef . current = undefined ;
58+ }
4659 // Fetch download URLs for each photo
4760 if ( c . photos && c . photos . length ) {
4861 const entries = await Promise . all (
@@ -61,11 +74,28 @@ export default function ClientPage() {
6174 setDownUrls ( { } ) ;
6275 }
6376 } catch ( e : unknown ) {
64- if ( e instanceof HttpError && ( e . status === 401 || e . status === 403 ) ) {
65- // Need or wrong password → open modal
66- setAskPassword ( true ) ;
67- // If we already tried with a password, mark error to display feedback
68- setPasswordError ( ! ! effectivePassword ) ;
77+ if ( e instanceof HttpError && ( e . status === 401 || e . status === 403 || e . status === 404 ) ) {
78+ // Behavior differs for entry flows (home/new) vs direct link
79+ if ( isEntryFlow ) {
80+ // Entry flow from /claim without password → redirect home with unified error
81+ router . push ( `/?error=idOrPassword&id=${ encodeURIComponent ( id ) } ` ) ;
82+ return ;
83+ }
84+ // Direct /claim/[id]
85+ if ( e . status === 401 || e . status === 403 ) {
86+ const hadPasswordAttempt = passwordSubmittedRef . current || ! ! effectivePassword ;
87+ passwordSubmittedRef . current = false ;
88+ pendingPasswordRef . current = undefined ;
89+ if ( hadPasswordAttempt ) {
90+ clearClaimPassword ( id ) ;
91+ }
92+ setAskPassword ( true ) ;
93+ setPasswordError ( hadPasswordAttempt ) ;
94+ } else if ( e . status === 404 ) {
95+ // Unknown ID: keep prompt, but don't show wrong-password error
96+ setAskPassword ( true ) ;
97+ setPasswordError ( false ) ;
98+ }
6999 } else {
70100 const msg = e instanceof Error ? e . message : '加载失败' ;
71101 setError ( msg ) ;
@@ -74,7 +104,7 @@ export default function ClientPage() {
74104 } finally {
75105 if ( lastLoadIdRef . current === thisLoadId ) setLoading ( false ) ;
76106 }
77- } , [ id , effectivePassword , passwordChecked ] ) ;
107+ } , [ id , effectivePassword , passwordChecked , isEntryFlow , router , askPassword ] ) ;
78108
79109 useEffect ( ( ) => {
80110 void load ( ) ;
@@ -135,19 +165,25 @@ export default function ClientPage() {
135165 setClaim ( updated ) ;
136166 } catch ( e : unknown ) {
137167 if ( e instanceof HttpError && e . status === 403 ) {
168+ const hadPasswordAttempt = ! ! effectivePassword ;
169+ passwordSubmittedRef . current = false ;
170+ pendingPasswordRef . current = undefined ;
171+ if ( hadPasswordAttempt ) {
172+ clearClaimPassword ( id ) ;
173+ }
138174 setAskPassword ( true ) ;
139- setPasswordError ( ! ! effectivePassword ) ;
175+ setPasswordError ( hadPasswordAttempt ) ;
140176 } else setError ( e instanceof Error ? e . message : '更新失败' ) ;
141177 } finally {
142178 setUpdating ( false ) ;
143179 }
144180 }
145181
146182 function copyLink ( ) {
147- if ( typeof window === 'undefined' ) return ;
148- const u = new URL ( window . location . href ) ;
149- u . searchParams . delete ( 'password' ) ;
150- navigator . clipboard ?. writeText ( u . toString ( ) ) . catch ( ( ) => { } ) ;
183+ if ( typeof window === 'undefined' || ! id ) return ;
184+ const link = new URL ( window . location . origin ) ;
185+ link . pathname = `/claim/ ${ encodeURIComponent ( id ) } ` ;
186+ navigator . clipboard ?. writeText ( link . toString ( ) ) . catch ( ( ) => { } ) ;
151187 }
152188
153189 // Require password: render only the password prompt, hide page content
@@ -159,8 +195,9 @@ export default function ClientPage() {
159195 < PasswordPrompt
160196 onSubmit = { ( pwd ) => {
161197 if ( ! id ) return ;
162- setClaimPassword ( id , pwd ) ;
198+ pendingPasswordRef . current = pwd ;
163199 setEffectivePassword ( pwd ) ;
200+ passwordSubmittedRef . current = true ;
164201 setAskPassword ( false ) ;
165202 setPasswordError ( false ) ;
166203 } }
0 commit comments