fix(frontend): keep browser API on same-origin rewrites in dev

Document that NEXT_PUBLIC_WAVELET_BACKEND_URL must stay unset for local
next dev so axios hits /api/* and Next rewrites proxy to the backend.
This commit is contained in:
ryan
2026-07-24 16:37:24 +08:00
parent a4588b05e0
commit 9f15f42b47
75 changed files with 5971 additions and 2450 deletions
+14 -6
View File
@@ -9,6 +9,7 @@ import {
ShieldQuestion,
} from 'lucide-react';
import { getCapToken } from '@/lib/cap-solver';
import { useTranslations } from 'next-intl';
type CapStatus = 'idle' | 'solving' | 'solved' | 'error';
@@ -38,6 +39,7 @@ export function CapWidget({
const [status, setStatus] = useState<CapStatus>('idle');
const [errorMsg, setErrorMsg] = useState('');
const solving = useRef(false);
const t = useTranslations('auth.capWidget');
const solve = async () => {
if (solving.current) return;
@@ -76,7 +78,9 @@ export function CapWidget({
onClick={() => void solve()}
>
<ShieldQuestion className='size-4 shrink-0 text-muted-foreground' />
<span className='flex-1 text-muted-foreground'>点击开始人机验证</span>
<span className='flex-1 text-muted-foreground'>
{t('clickToStart')}
</span>
</button>
)}
@@ -84,14 +88,16 @@ export function CapWidget({
{status === 'idle' && autoStart && (
<>
<ShieldAlert className='size-4 shrink-0 text-muted-foreground' />
<span className='text-muted-foreground'>等待人机验证…</span>
<span className='text-muted-foreground'>
{t('waitingVerification')}
</span>
</>
)}
{status === 'solving' && (
<>
<Loader2 className='size-4 shrink-0 animate-spin text-primary' />
<span className='text-muted-foreground'>正在完成人机验证…</span>
<span className='text-muted-foreground'>{t('verifying')}</span>
</>
)}
@@ -99,7 +105,7 @@ export function CapWidget({
<>
<CheckCircle2 className='size-4 shrink-0 text-green-500' />
<span className='text-green-600 dark:text-green-400'>
人机验证通过
{t('verificationPassed')}
</span>
<ShieldCheck className='ml-auto size-4 shrink-0 text-green-500' />
</>
@@ -113,9 +119,11 @@ export function CapWidget({
>
<ShieldAlert className='size-4 shrink-0 text-destructive' />
<span className='flex-1 text-destructive'>
{errorMsg || '人机验证失败'}
{errorMsg || t('verificationFailed')}
</span>
<span className='text-xs text-muted-foreground underline'>
{t('retry')}
</span>
<span className='text-xs text-muted-foreground underline'>重试</span>
</button>
)}
</div>
+1 -1
View File
@@ -114,7 +114,7 @@ export function RegisterForm() {
toast.error(t('closed'));
router.replace('/login');
}
}, [publicConfigQuery.isSuccess, registrationEnabled, router]);
}, [publicConfigQuery.isSuccess, registrationEnabled, router, t]);
const registerMutation = useMutation({
mutationFn: (req: RegisterRequest) => {
+3 -1
View File
@@ -5,6 +5,7 @@ import { Loader2 } from 'lucide-react';
import { ErrorPage } from '@/components/layout/error';
import { useUser } from '@/contexts/user-context';
import { useTranslations } from 'next-intl';
type RequireAuthProps = {
children: ReactNode;
@@ -49,6 +50,7 @@ type RequireAdminAuthProps = {
/** Guards admin routes after the shared shell has rendered. */
export function RequireAdminAuth({ children }: RequireAdminAuthProps) {
const { user, loading } = useUser();
const t = useTranslations('auth.requireAuth');
if (loading) {
return (
@@ -59,7 +61,7 @@ export function RequireAdminAuth({ children }: RequireAdminAuthProps) {
}
if (!user?.is_admin) {
return <ErrorPage title='访问被拒绝' message='您没有权限访问此页面' />;
return <ErrorPage title={t('accessDenied')} message={t('noPermission')} />;
}
return <>{children}</>;