@@ -12,8 +12,6 @@ import { useState, useEffect } from "react";
1212import { useRouter } from "next/navigation" ;
1313import { useTranslations } from "next-intl" ;
1414import { useAuth } from "@/lib/use-auth" ;
15- import { Header } from "@/app/components/Header" ;
16- import { Footer } from "@/app/components/Footer" ;
1715import { Button } from "@/components/ui/button" ;
1816import { Input } from "@/components/ui/input" ;
1917import { Label } from "@/components/ui/label" ;
@@ -44,6 +42,10 @@ export default function SubmitPage() {
4442 setSubmitting ( true ) ;
4543 setError ( null ) ;
4644
45+ // 15s 超时保护,避免 fetch 永远 hang 卡在 "提交中..."
46+ const ctrl = new AbortController ( ) ;
47+ const timer = setTimeout ( ( ) => ctrl . abort ( ) , 15_000 ) ;
48+
4749 try {
4850 // satoken 从 localStorage 读取,随请求头发送(与其他需鉴权的接口一致)
4951 const token =
@@ -59,14 +61,16 @@ export default function SubmitPage() {
5961 url : url . trim ( ) ,
6062 recommendation : recommendation . trim ( ) || undefined ,
6163 } ) ,
64+ signal : ctrl . signal ,
6265 } ) ;
6366
6467 if ( res . ok ) {
6568 // 提交成功:toast 反馈 + 跳用户分享列表页
6669 // 使用 alert 保持轻量(与 ReportButton 一致,无额外 toast provider 依赖)
6770 alert ( t ( "successToast" ) ) ;
68- if ( user ?. id ) {
69- router . push ( `/u/${ user . id } /shares` ) ;
71+ // 路由 param 是 [username] 不是 id,必须用 user.username 而不是 user.id
72+ if ( user ?. username ) {
73+ router . push ( `/u/${ user . username } /shares` ) ;
7074 } else {
7175 router . push ( "/feed" ) ;
7276 }
@@ -78,26 +82,27 @@ export default function SubmitPage() {
7882 `提交失败(HTTP ${ res . status } )` ,
7983 ) ;
8084 }
81- } catch {
82- setError ( "网络错误,请稍后重试" ) ;
85+ } catch ( e ) {
86+ if ( e instanceof DOMException && e . name === "AbortError" ) {
87+ setError ( "请求超时(15 秒未响应),请稍后重试" ) ;
88+ } else {
89+ setError ( "网络错误,请稍后重试" ) ;
90+ }
8391 } finally {
92+ clearTimeout ( timer ) ;
8493 setSubmitting ( false ) ;
8594 }
8695 }
8796
8897 // 认证加载中:渲染骨架避免布局跳动
8998 if ( status === "loading" ) {
9099 return (
91- < >
92- < Header />
93- < main className = "pt-32 pb-16 bg-[var(--background)] min-h-screen" >
94- < div className = "max-w-xl mx-auto px-6 lg:px-8 animate-pulse" >
95- < div className = "h-8 bg-neutral-100 dark:bg-neutral-900 rounded mb-4" />
96- < div className = "h-4 bg-neutral-100 dark:bg-neutral-900 rounded w-2/3" />
97- </ div >
98- </ main >
99- < Footer />
100- </ >
100+ < main className = "pt-32 pb-16 bg-[var(--background)] min-h-screen" >
101+ < div className = "max-w-xl mx-auto px-6 lg:px-8 animate-pulse" >
102+ < div className = "h-8 bg-neutral-100 dark:bg-neutral-900 rounded mb-4" />
103+ < div className = "h-4 bg-neutral-100 dark:bg-neutral-900 rounded w-2/3" />
104+ </ div >
105+ </ main >
101106 ) ;
102107 }
103108
@@ -107,96 +112,90 @@ export default function SubmitPage() {
107112 }
108113
109114 return (
110- < >
111- < Header />
112- < main className = "pt-32 pb-16 bg-[var(--background)] min-h-screen" >
113- < div className = "max-w-xl mx-auto px-6 lg:px-8" >
114- { /* 页头 */ }
115- < header className = "border-t-4 border-[var(--foreground)] pt-6 mb-10" >
116- < div className = "font-mono text-[10px] uppercase tracking-[0.3em] text-neutral-500" >
117- Community · Feed · Submit
118- </ div >
119- < h1 className = "font-serif text-4xl md:text-5xl font-black uppercase mt-2 tracking-tight text-[var(--foreground)]" >
120- { t ( "title" ) }
121- </ h1 >
122- </ header >
123-
124- { /* 提交表单 */ }
125- < form onSubmit = { handleSubmit } className = "space-y-6" >
126- { /* URL 输入 */ }
127- < div className = "space-y-2" >
115+ < main className = "pt-32 pb-16 bg-[var(--background)] min-h-screen" >
116+ < div className = "max-w-xl mx-auto px-6 lg:px-8" >
117+ { /* 页头 */ }
118+ < header className = "border-t-4 border-[var(--foreground)] pt-6 mb-10" >
119+ < div className = "font-mono text-[10px] uppercase tracking-[0.3em] text-neutral-500" >
120+ Community · Feed · Submit
121+ </ div >
122+ < h1 className = "font-serif text-4xl md:text-5xl font-black uppercase mt-2 tracking-tight text-[var(--foreground)]" >
123+ { t ( "title" ) }
124+ </ h1 >
125+ </ header >
126+
127+ { /* 提交表单 */ }
128+ < form onSubmit = { handleSubmit } className = "space-y-6" >
129+ { /* URL 输入 */ }
130+ < div className = "space-y-2" >
131+ < Label
132+ htmlFor = "url"
133+ className = "font-mono text-xs uppercase tracking-widest"
134+ >
135+ { t ( "urlLabel" ) }
136+ </ Label >
137+ < Input
138+ id = "url"
139+ type = "url"
140+ placeholder = { t ( "urlPlaceholder" ) }
141+ value = { url }
142+ onChange = { ( e ) => setUrl ( e . target . value ) }
143+ required
144+ className = "rounded-none border-[var(--foreground)] focus-visible:ring-0 focus-visible:border-[var(--foreground)] bg-[var(--background)] text-[var(--foreground)]"
145+ />
146+ </ div >
147+
148+ { /* 推荐语 textarea */ }
149+ < div className = "space-y-2" >
150+ < div className = "flex items-center justify-between" >
128151 < Label
129- htmlFor = "url "
152+ htmlFor = "recommendation "
130153 className = "font-mono text-xs uppercase tracking-widest"
131154 >
132- { t ( "urlLabel " ) }
155+ { t ( "recommendationLabel " ) }
133156 </ Label >
134- < Input
135- id = "url"
136- type = "url"
137- placeholder = { t ( "urlPlaceholder" ) }
138- value = { url }
139- onChange = { ( e ) => setUrl ( e . target . value ) }
140- required
141- className = "rounded-none border-[var(--foreground)] focus-visible:ring-0 focus-visible:border-[var(--foreground)] bg-[var(--background)] text-[var(--foreground)]"
142- />
143- </ div >
144-
145- { /* 推荐语 textarea */ }
146- < div className = "space-y-2" >
147- < div className = "flex items-center justify-between" >
148- < Label
149- htmlFor = "recommendation"
150- className = "font-mono text-xs uppercase tracking-widest"
151- >
152- { t ( "recommendationLabel" ) }
153- </ Label >
154- { /* 字数计数 */ }
155- < span className = "font-mono text-[10px] text-neutral-400" >
156- { t ( "charCount" , { count : recommendation . length } ) }
157- </ span >
158- </ div >
159- < textarea
160- id = "recommendation"
161- className = "w-full border border-[var(--foreground)] p-3 text-sm resize-none h-28 focus:outline-none focus:border-[var(--foreground)] bg-[var(--background)] text-[var(--foreground)] placeholder:text-neutral-400"
162- placeholder = { t ( "recommendationPlaceholder" ) }
163- value = { recommendation }
164- onChange = { ( e ) => {
165- // 限制最多 200 字
166- if ( e . target . value . length <= 200 ) {
167- setRecommendation ( e . target . value ) ;
168- }
169- } }
170- maxLength = { 200 }
171- />
157+ { /* 字数计数 */ }
158+ < span className = "font-mono text-[10px] text-neutral-400" >
159+ { t ( "charCount" , { count : recommendation . length } ) }
160+ </ span >
172161 </ div >
162+ < textarea
163+ id = "recommendation"
164+ className = "w-full border border-[var(--foreground)] p-3 text-sm resize-none h-28 focus:outline-none focus:border-[var(--foreground)] bg-[var(--background)] text-[var(--foreground)] placeholder:text-neutral-400"
165+ placeholder = { t ( "recommendationPlaceholder" ) }
166+ value = { recommendation }
167+ onChange = { ( e ) => {
168+ // 限制最多 200 字
169+ if ( e . target . value . length <= 200 ) {
170+ setRecommendation ( e . target . value ) ;
171+ }
172+ } }
173+ maxLength = { 200 }
174+ />
175+ </ div >
173176
174- { /* 错误提示 */ }
175- { error && (
176- < p className = "text-sm text-[#CC0000] font-mono" > { error } </ p >
177- ) }
178-
179- { /* 提交按钮 */ }
180- < div className = "flex items-center gap-4" >
181- < Button
182- type = "submit"
183- disabled = { submitting || ! url . trim ( ) }
184- className = "rounded-none px-8 py-3 font-sans text-xs uppercase tracking-widest font-bold bg-[var(--foreground)] text-[var(--background)] hover:bg-[var(--background)] hover:text-[var(--foreground)] border border-[var(--foreground)] transition-all duration-200 h-auto disabled:opacity-40"
185- >
186- { submitting ? t ( "submitting" ) : t ( "submitButton" ) }
187- </ Button >
188- < button
189- type = "button"
190- onClick = { ( ) => router . back ( ) }
191- className = "font-mono text-xs uppercase tracking-widest text-neutral-500 hover:text-[var(--foreground)] transition-colors"
192- >
193- 取消
194- </ button >
195- </ div >
196- </ form >
197- </ div >
198- </ main >
199- < Footer />
200- </ >
177+ { /* 错误提示 */ }
178+ { error && < p className = "text-sm text-[#CC0000] font-mono" > { error } </ p > }
179+
180+ { /* 提交按钮 */ }
181+ < div className = "flex items-center gap-4" >
182+ < Button
183+ type = "submit"
184+ disabled = { submitting || ! url . trim ( ) }
185+ className = "rounded-none px-8 py-3 font-sans text-xs uppercase tracking-widest font-bold bg-[var(--foreground)] text-[var(--background)] hover:bg-[var(--background)] hover:text-[var(--foreground)] border border-[var(--foreground)] transition-all duration-200 h-auto disabled:opacity-40"
186+ >
187+ { submitting ? t ( "submitting" ) : t ( "submitButton" ) }
188+ </ Button >
189+ < button
190+ type = "button"
191+ onClick = { ( ) => router . back ( ) }
192+ className = "font-mono text-xs uppercase tracking-widest text-neutral-500 hover:text-[var(--foreground)] transition-colors"
193+ >
194+ 取消
195+ </ button >
196+ </ div >
197+ </ form >
198+ </ div >
199+ </ main >
201200 ) ;
202201}
0 commit comments