feat: fetchWrapper 마이그레이션 및 토큰 리프레시 캐싱 구현
- 40+ actions.ts 파일을 fetchWrapper 패턴으로 마이그레이션 - 토큰 리프레시 캐싱 로직 추가 (refresh-token.ts) - ApiErrorContext 추가로 전역 에러 처리 개선 - HR EmployeeForm 컴포넌트 개선 - 참조함(ReferenceBox) 기능 수정 - juil 테스트 URL 페이지 추가 - claudedocs 문서 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,37 +1,37 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
// ===== API 헤더 생성 =====
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
// ===== 계정 탈퇴 =====
|
||||
export async function withdrawAccount(): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/users/withdraw`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '계정 탈퇴에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -55,19 +55,32 @@ export async function withdrawAccount(): Promise<{
|
||||
export async function suspendTenant(): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/tenants/suspend`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '사용 중지에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Account, AccountFormData, AccountStatus } from './types';
|
||||
import { BANK_LABELS } from './types';
|
||||
|
||||
@@ -39,19 +39,6 @@ interface ApiSingleResponse {
|
||||
data: BankAccountApiData;
|
||||
}
|
||||
|
||||
// ===== API 헤더 생성 =====
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 데이터 변환: API → Frontend =====
|
||||
function transformApiToFrontend(apiData: BankAccountApiData): Account {
|
||||
return {
|
||||
@@ -91,9 +78,9 @@ export async function getBankAccounts(params?: {
|
||||
data?: Account[];
|
||||
meta?: PaginationMeta;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (params?.page) searchParams.set('page', params.page.toString());
|
||||
@@ -102,12 +89,23 @@ export async function getBankAccounts(params?: {
|
||||
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-accounts?${searchParams.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
const { response, error } = await serverFetch(url, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '계좌 목록 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiListResponse = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -127,18 +125,29 @@ export async function getBankAccount(id: number): Promise<{
|
||||
success: boolean;
|
||||
data?: Account;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-accounts/${id}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '계좌 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiSingleResponse = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -158,20 +167,31 @@ export async function createBankAccount(data: AccountFormData): Promise<{
|
||||
success: boolean;
|
||||
data?: Account;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const apiData = transformFrontendToApi(data);
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-accounts`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '계좌 등록에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiSingleResponse = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -194,20 +214,31 @@ export async function updateBankAccount(
|
||||
success: boolean;
|
||||
data?: Account;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const apiData = transformFrontendToApi(data);
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-accounts/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '계좌 수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiSingleResponse = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -226,17 +257,28 @@ export async function updateBankAccount(
|
||||
export async function deleteBankAccount(id: number): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-accounts/${id}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '계좌 삭제에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -255,17 +297,28 @@ export async function toggleBankAccountStatus(id: number): Promise<{
|
||||
success: boolean;
|
||||
data?: Account;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-accounts/${id}/toggle`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '상태 변경에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiSingleResponse = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -285,17 +338,28 @@ export async function setPrimaryBankAccount(id: number): Promise<{
|
||||
success: boolean;
|
||||
data?: Account;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/bank-accounts/${id}/set-primary`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '대표 계좌 설정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result: ApiSingleResponse = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://sam.kr:8080';
|
||||
|
||||
// ===== API Helper =====
|
||||
async function getAuthHeaders() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-KEY': process.env.NEXT_PUBLIC_API_KEY || '',
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
};
|
||||
}
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
// ===== 타입 정의 =====
|
||||
|
||||
@@ -79,21 +65,30 @@ export async function getAttendanceSetting(): Promise<{
|
||||
success: boolean;
|
||||
data?: AttendanceSettingFormData;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getAuthHeaders();
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/settings/attendance`,
|
||||
{
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/settings/attendance`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: errorData.message || `API 오류: ${response.status}`,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
const errorData = await response?.json().catch(() => ({}));
|
||||
return {
|
||||
success: false,
|
||||
error: errorData?.message || `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,21 +116,30 @@ export async function updateAttendanceSetting(
|
||||
success: boolean;
|
||||
data?: AttendanceSettingFormData;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getAuthHeaders();
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/settings/attendance`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(transformToApi(data)),
|
||||
}
|
||||
);
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/settings/attendance`, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(transformToApi(data)),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: errorData.message || `API 오류: ${response.status}`,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
const errorData = await response?.json().catch(() => ({}));
|
||||
return {
|
||||
success: false,
|
||||
error: errorData?.message || `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { CompanyFormData } from './types';
|
||||
|
||||
// API 응답 타입
|
||||
@@ -32,19 +32,6 @@ interface TenantApiData {
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
// API 헤더 생성
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 테넌트 정보 조회
|
||||
*/
|
||||
@@ -52,18 +39,29 @@ export async function getCompanyInfo(): Promise<{
|
||||
success: boolean;
|
||||
data?: CompanyFormData & { tenantId: number };
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/tenants`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '회사 정보 조회에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -90,20 +88,31 @@ export async function updateCompanyInfo(
|
||||
success: boolean;
|
||||
data?: CompanyFormData;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const apiData = transformFrontendToApi(tenantId, data);
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/tenants`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return { success: false, error: '회사 정보 수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { LeavePolicySettings } from './types';
|
||||
|
||||
// ===== API 헤더 생성 =====
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
|
||||
// ===== API 응답 타입 =====
|
||||
interface LeavePolicyApi {
|
||||
id: number;
|
||||
@@ -70,22 +57,29 @@ export async function getLeavePolicy(): Promise<{
|
||||
success: boolean;
|
||||
data?: LeavePolicySettings;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/leave-policy`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
const { response, error } = await serverFetch(url, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('[LeavePolicyActions] GET error:', response.status);
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `API 오류: ${response.status}`,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[LeavePolicyActions] GET error:', response?.status);
|
||||
return {
|
||||
success: false,
|
||||
error: `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,24 +112,30 @@ export async function updateLeavePolicy(data: Partial<LeavePolicySettings>): Pro
|
||||
success: boolean;
|
||||
data?: LeavePolicySettings;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/leave-policy`;
|
||||
|
||||
const apiData = transformToApi(data);
|
||||
|
||||
const response = await fetch(url, {
|
||||
const { response, error } = await serverFetch(url, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('[LeavePolicyActions] PUT error:', response.status);
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `API 오류: ${response.status}`,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[LeavePolicyActions] PUT error:', response?.status);
|
||||
return {
|
||||
success: false,
|
||||
error: `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +1,36 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { NotificationSettings } from './types';
|
||||
import { DEFAULT_NOTIFICATION_SETTINGS } from './types';
|
||||
|
||||
// ===== API 헤더 생성 =====
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 알림 설정 조회 =====
|
||||
export async function getNotificationSettings(): Promise<{
|
||||
success: boolean;
|
||||
data: NotificationSettings;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/settings/notifications`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('[NotificationActions] GET settings error:', response.status);
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
data: DEFAULT_NOTIFICATION_SETTINGS,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
console.warn('[NotificationActions] GET settings error:', response?.status);
|
||||
return {
|
||||
success: true,
|
||||
data: DEFAULT_NOTIFICATION_SETTINGS,
|
||||
@@ -69,20 +63,33 @@ export async function getNotificationSettings(): Promise<{
|
||||
// ===== 알림 설정 저장 =====
|
||||
export async function saveNotificationSettings(
|
||||
settings: NotificationSettings
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const apiData = transformFrontendToApi(settings);
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/settings/notifications`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '알림 설정 저장에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { PaymentApiData, PaymentHistory } from './types';
|
||||
import { transformApiToFrontend } from './utils';
|
||||
|
||||
// ===== API 헤더 생성 =====
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 결제 목록 조회 =====
|
||||
export async function getPayments(params?: {
|
||||
page?: number;
|
||||
@@ -35,10 +22,9 @@ export async function getPayments(params?: {
|
||||
total: number;
|
||||
};
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
// 쿼리 파라미터 생성
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params?.page) searchParams.append('page', String(params.page));
|
||||
@@ -51,12 +37,30 @@ export async function getPayments(params?: {
|
||||
const queryString = searchParams.toString();
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/payments${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
const { response, error } = await serverFetch(url, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
data: [],
|
||||
pagination: { currentPage: 1, lastPage: 1, perPage: 20, total: 0 },
|
||||
error: '결제 내역을 불러오는데 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -144,19 +148,32 @@ export async function getPaymentStatement(id: string): Promise<{
|
||||
total: number;
|
||||
};
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/payments/${id}/statement`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '명세서를 불러오는데 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { Popup, PopupFormData } from './types';
|
||||
import { transformApiToFrontend, transformFrontendToApi, type PopupApiData } from './utils';
|
||||
|
||||
@@ -25,25 +25,6 @@ interface ApiResponse<T> {
|
||||
message: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 헬퍼 함수
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* API 헤더 생성
|
||||
*/
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// API 함수
|
||||
// ============================================
|
||||
@@ -65,7 +46,6 @@ export async function getPopups(params?: {
|
||||
status?: string;
|
||||
}): Promise<Popup[]> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
if (params?.page) searchParams.set('page', String(params.page));
|
||||
@@ -76,12 +56,16 @@ export async function getPopups(params?: {
|
||||
|
||||
const url = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/popups?${searchParams.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
const { response, error } = await serverFetch(url, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (error || !response) {
|
||||
console.error('[PopupActions] GET list error:', error?.message);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('[PopupActions] GET list error:', response.status);
|
||||
return [];
|
||||
@@ -106,17 +90,19 @@ export async function getPopups(params?: {
|
||||
*/
|
||||
export async function getPopupById(id: string): Promise<Popup | null> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/popups/${id}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
console.error('[PopupActions] GET popup error:', error?.message);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('[PopupActions] GET popup error:', response.status);
|
||||
return null;
|
||||
@@ -140,22 +126,35 @@ export async function getPopupById(id: string): Promise<Popup | null> {
|
||||
*/
|
||||
export async function createPopup(
|
||||
data: PopupFormData
|
||||
): Promise<{ success: boolean; data?: Popup; error?: string }> {
|
||||
): Promise<{ success: boolean; data?: Popup; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const apiData = transformFrontendToApi(data);
|
||||
|
||||
console.log('[PopupActions] POST popup request:', apiData);
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/popups`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '팝업 등록에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[PopupActions] POST popup response:', result);
|
||||
|
||||
@@ -185,22 +184,35 @@ export async function createPopup(
|
||||
export async function updatePopup(
|
||||
id: string,
|
||||
data: PopupFormData
|
||||
): Promise<{ success: boolean; data?: Popup; error?: string }> {
|
||||
): Promise<{ success: boolean; data?: Popup; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
const apiData = transformFrontendToApi(data);
|
||||
|
||||
console.log('[PopupActions] PUT popup request:', apiData);
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/popups/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(apiData),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '팝업 수정에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[PopupActions] PUT popup response:', result);
|
||||
|
||||
@@ -227,18 +239,30 @@ export async function updatePopup(
|
||||
/**
|
||||
* 팝업 삭제
|
||||
*/
|
||||
export async function deletePopup(id: string): Promise<{ success: boolean; error?: string }> {
|
||||
export async function deletePopup(id: string): Promise<{ success: boolean; error?: string; __authError?: boolean }> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/popups/${id}`,
|
||||
{
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '팝업 삭제에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('[PopupActions] DELETE popup response:', result);
|
||||
|
||||
|
||||
@@ -1,40 +1,42 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
import type { SubscriptionApiData, UsageApiData, SubscriptionInfo } from './types';
|
||||
import { transformApiToFrontend } from './utils';
|
||||
|
||||
// ===== API 헤더 생성 =====
|
||||
async function getApiHeaders(): Promise<HeadersInit> {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': token ? `Bearer ${token}` : '',
|
||||
'X-API-KEY': process.env.API_KEY || '',
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 현재 활성 구독 조회 =====
|
||||
export async function getCurrentSubscription(): Promise<{
|
||||
success: boolean;
|
||||
data: SubscriptionApiData | null;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/subscriptions/current`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: '구독 정보를 불러오는데 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -64,19 +66,34 @@ export async function getUsage(): Promise<{
|
||||
success: boolean;
|
||||
data: UsageApiData | null;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/subscriptions/usage`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
error: '사용량 정보를 불러오는데 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -108,19 +125,32 @@ export async function cancelSubscription(
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/subscriptions/${id}/cancel`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ reason }),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '구독 취소에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
@@ -147,19 +177,32 @@ export async function requestDataExport(
|
||||
success: boolean;
|
||||
data?: { id: number; status: string };
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getApiHeaders();
|
||||
|
||||
const response = await fetch(
|
||||
const { response, error } = await serverFetch(
|
||||
`${process.env.NEXT_PUBLIC_API_URL}/api/v1/subscriptions/export`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ export_type: exportType }),
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
return {
|
||||
success: false,
|
||||
error: '내보내기 요청에 실패했습니다.',
|
||||
};
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
'use server';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { serverFetch } from '@/lib/api/fetch-wrapper';
|
||||
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://sam.kr:8080';
|
||||
|
||||
// ===== API Helper =====
|
||||
async function getAuthHeaders() {
|
||||
const cookieStore = await cookies();
|
||||
const token = cookieStore.get('access_token')?.value;
|
||||
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-KEY': process.env.NEXT_PUBLIC_API_KEY || '',
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 타입 정의 =====
|
||||
|
||||
// API 응답 타입
|
||||
@@ -99,21 +87,27 @@ export async function getWorkSetting(): Promise<{
|
||||
success: boolean;
|
||||
data?: WorkSettingFormData;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getAuthHeaders();
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/settings/work`, {
|
||||
const { response, error } = await serverFetch(`${API_BASE_URL}/api/v1/settings/work`, {
|
||||
method: 'GET',
|
||||
headers,
|
||||
cache: 'no-store',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: errorData.message || `API 오류: ${response.status}`,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
const errorData = await response?.json().catch(() => ({}));
|
||||
return {
|
||||
success: false,
|
||||
error: errorData?.message || `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -141,21 +135,27 @@ export async function updateWorkSetting(
|
||||
success: boolean;
|
||||
data?: WorkSettingFormData;
|
||||
error?: string;
|
||||
__authError?: boolean;
|
||||
}> {
|
||||
try {
|
||||
const headers = await getAuthHeaders();
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/api/v1/settings/work`, {
|
||||
const { response, error } = await serverFetch(`${API_BASE_URL}/api/v1/settings/work`, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify(transformToApi(data)),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
if (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: errorData.message || `API 오류: ${response.status}`,
|
||||
error: error.message,
|
||||
__authError: error.code === 'UNAUTHORIZED',
|
||||
};
|
||||
}
|
||||
|
||||
if (!response || !response.ok) {
|
||||
const errorData = await response?.json().catch(() => ({}));
|
||||
return {
|
||||
success: false,
|
||||
error: errorData?.message || `API 오류: ${response?.status}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user