fix(WEB): 토큰 만료 시 무한 로딩 대신 로그인 리다이렉트 처리
- 52개 이상의 컴포넌트에 isNextRedirectError 처리 추가 - Server Action의 redirect() 에러가 catch 블록에서 삼켜지는 문제 해결 - access_token + refresh_token 모두 만료 시 정상적으로 로그인 페이지로 리다이렉트 수정된 영역: - accounting: 10개 컴포넌트 - production: 12개 컴포넌트 - hr: 5개 컴포넌트 - settings: 8개 컴포넌트 - approval: 5개 컴포넌트 - items: 20개+ 컴포넌트 - board: 5개 컴포넌트 - quality: 4개 컴포넌트 - material, outbound, quotes 등 기타 컴포넌트 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ArrowLeft, Truck, Loader2, AlertCircle } from 'lucide-react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -36,6 +37,7 @@ import type {
|
||||
LogisticsOption,
|
||||
VehicleTonnageOption,
|
||||
} from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
// 고정 옵션 (클라이언트에서 관리)
|
||||
const priorityOptions: { value: ShipmentPriority; label: string }[] = [
|
||||
@@ -101,6 +103,7 @@ export function ShipmentCreate() {
|
||||
setVehicleTonnageOptions(tonnageResult.data);
|
||||
}
|
||||
} catch (err) {
|
||||
if (isNextRedirectError(err)) throw err;
|
||||
console.error('[ShipmentCreate] loadOptions error:', err);
|
||||
setError('옵션 데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
@@ -166,6 +169,7 @@ export function ShipmentCreate() {
|
||||
setValidationErrors([result.error || '출하 등록에 실패했습니다.']);
|
||||
}
|
||||
} catch (err) {
|
||||
if (isNextRedirectError(err)) throw err;
|
||||
console.error('[ShipmentCreate] handleSubmit error:', err);
|
||||
setValidationErrors(['저장 중 오류가 발생했습니다.']);
|
||||
} finally {
|
||||
@@ -177,9 +181,7 @@ export function ShipmentCreate() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||||
</div>
|
||||
<ContentLoadingSpinner text="출하 등록 정보를 불러오는 중..." />
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
AlertCircle,
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -60,6 +61,7 @@ import { ShippingSlip } from './documents/ShippingSlip';
|
||||
import { TransactionStatement } from './documents/TransactionStatement';
|
||||
import { DeliveryConfirmation } from './documents/DeliveryConfirmation';
|
||||
import { printArea } from '@/lib/print-utils';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
interface ShipmentDetailProps {
|
||||
id: string;
|
||||
@@ -90,6 +92,7 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) {
|
||||
setError(result.error || '출하 정보를 찾을 수 없습니다.');
|
||||
}
|
||||
} catch (err) {
|
||||
if (isNextRedirectError(err)) throw err;
|
||||
console.error('[ShipmentDetail] loadData error:', err);
|
||||
setError('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
@@ -123,6 +126,7 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) {
|
||||
alert(result.error || '삭제에 실패했습니다.');
|
||||
}
|
||||
} catch (err) {
|
||||
if (isNextRedirectError(err)) throw err;
|
||||
console.error('[ShipmentDetail] handleDelete error:', err);
|
||||
alert('삭제 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
@@ -151,9 +155,7 @@ export function ShipmentDetail({ id }: ShipmentDetailProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||||
</div>
|
||||
<ContentLoadingSpinner text="출하 정보를 불러오는 중..." />
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ArrowLeft, Truck, Loader2, AlertCircle } from 'lucide-react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
@@ -41,6 +42,7 @@ import type {
|
||||
LogisticsOption,
|
||||
VehicleTonnageOption,
|
||||
} from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
// 고정 옵션 (클라이언트에서 관리)
|
||||
const priorityOptions: { value: ShipmentPriority; label: string }[] = [
|
||||
@@ -138,6 +140,7 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
setVehicleTonnageOptions(tonnageResult.data);
|
||||
}
|
||||
} catch (err) {
|
||||
if (isNextRedirectError(err)) throw err;
|
||||
console.error('[ShipmentEdit] loadData error:', err);
|
||||
setError('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
@@ -203,6 +206,7 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
setValidationErrors([result.error || '출하 수정에 실패했습니다.']);
|
||||
}
|
||||
} catch (err) {
|
||||
if (isNextRedirectError(err)) throw err;
|
||||
console.error('[ShipmentEdit] handleSubmit error:', err);
|
||||
setValidationErrors(['저장 중 오류가 발생했습니다.']);
|
||||
} finally {
|
||||
@@ -214,9 +218,7 @@ export function ShipmentEdit({ id }: ShipmentEditProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||||
</div>
|
||||
<ContentLoadingSpinner text="출고 정보를 불러오는 중..." />
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ import {
|
||||
Plus,
|
||||
Check,
|
||||
X,
|
||||
Loader2,
|
||||
AlertCircle,
|
||||
} from 'lucide-react';
|
||||
import { ContentLoadingSpinner } from '@/components/ui/loading-spinner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { TableCell, TableRow } from '@/components/ui/table';
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
DELIVERY_METHOD_LABELS,
|
||||
} from './types';
|
||||
import type { ShipmentItem, ShipmentStatus, ShipmentStats, ShipmentStatusStats } from './types';
|
||||
import { isNextRedirectError } from '@/lib/utils/redirect-error';
|
||||
|
||||
// 페이지당 항목 수
|
||||
const ITEMS_PER_PAGE = 20;
|
||||
@@ -94,6 +95,7 @@ export function ShipmentList() {
|
||||
setStatusStats(statusStatsResult.data);
|
||||
}
|
||||
} catch (err) {
|
||||
if (isNextRedirectError(err)) throw err;
|
||||
console.error('[ShipmentList] loadData error:', err);
|
||||
setError('데이터를 불러오는 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
@@ -336,11 +338,7 @@ export function ShipmentList() {
|
||||
|
||||
// 로딩 상태 표시 (모든 Hooks 선언 후)
|
||||
if (isLoading && items.length === 0) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[400px]">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-primary" />
|
||||
</div>
|
||||
);
|
||||
return <ContentLoadingSpinner text="출하 목록을 불러오는 중..." />;
|
||||
}
|
||||
|
||||
// 에러 상태 표시
|
||||
|
||||
Reference in New Issue
Block a user