Files
sam-react-prod/src/components/settings/AccountInfoManagement/actions.ts
유병철 ea6ca335f1 feat: CSP 다음/카카오 도메인 허용 + 입고 성적서 파일 백엔드 연동 + 팝업 이미지 중앙정렬
- middleware CSP: *.kakao.com, *.kakaocdn.net 추가 (다음 주소찾기 차단 해결)
- frame-src에 'self' 추가
- 공지 팝업 이미지 중앙정렬 ([&_img]:mx-auto)
- HR 사원관리, 결재, 품목, 생산 등 다수 개선
- API 에러 핸들링 및 JSON 파싱 안정화
2026-03-11 22:32:58 +09:00

157 lines
5.1 KiB
TypeScript

'use server';
import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action';
import type { AccountInfo, TermsAgreement, MarketingConsent } from './types';
const API_URL = process.env.NEXT_PUBLIC_API_URL;
/**
* 상대 경로를 표시 가능한 URL로 변환
* R2 전환 후: /api/proxy/files/{id}/view 사용
* 레거시 경로는 그대로 반환 (표시 불가할 수 있음)
*/
function toAbsoluteUrl(path: string | undefined): string | undefined {
if (!path) return undefined;
// 이미 절대 URL이면 그대로 반환
if (path.startsWith('http://') || path.startsWith('https://')) {
return path;
}
// 프록시 경로면 그대로 반환
if (path.startsWith('/api/proxy/')) {
return path;
}
// R2 전환 후 /storage/ 직접 접근 불가 — 경로만 보존
return path;
}
// ===== 계정 정보 조회 =====
export async function getAccountInfo(): Promise<{
success: boolean;
data?: {
accountInfo: AccountInfo;
termsAgreements: TermsAgreement[];
marketingConsent: MarketingConsent;
};
error?: string;
__authError?: boolean;
}> {
// 1. 사용자 기본 정보 조회
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const userResult = await executeServerAction<any>({
url: `${API_URL}/api/v1/users/me`,
errorMessage: '계정 정보를 불러올 수 없습니다.',
});
if (userResult.__authError) return { success: false, __authError: true };
if (!userResult.success || !userResult.data) return { success: false, error: userResult.error };
const user = userResult.data;
// 2. 프로필 정보 조회 (프로필 이미지 포함 - 실패해도 계속 진행)
let profileImage: string | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const profileResult = await executeServerAction<any>({
url: `${API_URL}/api/v1/profiles/me`,
errorMessage: '프로필 조회 실패',
});
if (profileResult.success && profileResult.data) {
profileImage = toAbsoluteUrl(profileResult.data.profile_photo_path);
}
return {
success: true,
data: {
accountInfo: {
id: user.id?.toString() || '',
email: user.email || '',
profileImage,
role: user.role?.name || user.role || '',
status: user.status || 'active',
isTenantMaster: user.is_tenant_master || false,
createdAt: user.created_at || '',
updatedAt: user.updated_at || '',
},
termsAgreements: user.terms_agreements || [],
marketingConsent: user.marketing_consent || {
email: { agreed: false },
sms: { agreed: false },
},
},
};
}
// ===== 계정 탈퇴 =====
export async function withdrawAccount(password: string): Promise<ActionResult> {
return executeServerAction({
url: `${API_URL}/api/v1/users/withdraw`,
method: 'POST',
body: { password },
errorMessage: '계정 탈퇴에 실패했습니다.',
});
}
// ===== 테넌트 사용 중지 =====
export async function suspendTenant(): Promise<ActionResult> {
return executeServerAction({
url: `${API_URL}/api/v1/tenants/suspend`,
method: 'POST',
body: {},
errorMessage: '사용 중지에 실패했습니다.',
});
}
// ===== 약관 동의 수정 =====
export async function updateAgreements(
agreements: Array<{ type: string; agreed: boolean }>
): Promise<ActionResult> {
return executeServerAction({
url: `${API_URL}/api/v1/account/agreements`,
method: 'PUT',
body: { agreements },
errorMessage: '약관 동의 수정에 실패했습니다.',
});
}
// ===== 프로필 이미지 업로드 =====
export async function uploadProfileImage(formData: FormData): Promise<{
success: boolean;
data?: { imageUrl: string };
error?: string;
__authError?: boolean;
}> {
// 1. 파일 업로드
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const uploadResult = await executeServerAction<any>({
url: `${API_URL}/api/v1/files/upload`,
method: 'POST',
body: formData,
errorMessage: '파일 업로드에 실패했습니다.',
});
if (uploadResult.__authError) return { success: false, __authError: true };
if (!uploadResult.success || !uploadResult.data) return { success: false, error: uploadResult.error };
const uploadedPath = uploadResult.data.file_path || uploadResult.data.path || uploadResult.data.url;
if (!uploadedPath) return { success: false, error: '업로드된 파일 경로를 가져올 수 없습니다.' };
// 2. 프로필 업데이트 (업로드된 파일 경로로)
const updateResult = await executeServerAction({
url: `${API_URL}/api/v1/profiles/me`,
method: 'PATCH',
body: { profile_photo_path: uploadedPath },
errorMessage: '프로필 업데이트에 실패했습니다.',
});
if (updateResult.__authError) return { success: false, __authError: true };
if (!updateResult.success) return { success: false, error: updateResult.error };
// R2 전환: file_id 기반 프록시 경로 사용
const fileId = uploadResult.data.id;
const viewUrl = fileId
? `/api/proxy/files/${fileId}/view`
: uploadedPath;
return {
success: true,
data: { imageUrl: viewUrl },
};
}