diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 81f200e..3156a87 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,23 +1,26 @@ import Link from "next/link"; import { SocialLoginGroup } from "@/features/auth/components/SocialLoginGroup"; +import { copy } from '@/shared/i18n'; export default function LoginPage() { + const { login } = copy; + return (
{/* 상단 로고 (홈으로 돌아가기) */} - 🪴 VibeRoom + 🪴 {copy.appName} {/* 로그인 카드 컨테이너 */}
-

다시 오셨군요!

+

{login.title}

- 비밀번호를 외울 필요 없이,
- 사용 중인 계정으로 3초 만에 시작하세요. + {login.descriptionFirstLine}
+ {login.descriptionSecondLine}

@@ -28,8 +31,8 @@ export default function LoginPage() {
- 로그인함으로써 VibeRoom의
- 이용약관개인정보처리방침에 동의하게 됩니다. + {login.agreementPrefix}
+ {login.terms} {login.agreementAnd} {login.privacy}{login.agreementSuffix}
@@ -37,4 +40,4 @@ export default function LoginPage() {
); -} \ No newline at end of file +} diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 1bd1f82..deef730 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -3,6 +3,7 @@ import { FormEvent, useEffect, useState } from 'react'; import { adminApi, type SceneMediaAssetUploadResponse, type SoundMediaAssetUploadResponse } from '@/features/admin/api/adminApi'; import type { AuthResponse } from '@/features/auth/types'; +import { copy } from '@/shared/i18n'; import { Button } from '@/shared/ui'; const ADMIN_STORAGE_KEY = 'vr_admin_session'; @@ -20,20 +21,7 @@ type NavItem = { section: string; }; -const navItems: NavItem[] = [ - { - id: 'scene', - title: '이미지 등록', - subtitle: 'Scene background assets', - section: 'MEDIA', - }, - { - id: 'sound', - title: '오디오 등록', - subtitle: 'Loop and preview assets', - section: 'MEDIA', - }, -]; +const navItems: NavItem[] = [...copy.admin.navItems]; const readStoredSession = (): AuthResponse | null => { if (typeof window === 'undefined') { @@ -76,48 +64,26 @@ const textareaClassName = 'min-h-32 w-full rounded-xl border border-slate-200 bg-white px-3 py-3 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-sky-400 focus:ring-2 focus:ring-sky-100'; const getViewMeta = (view: AdminView) => { - if (view === 'scene') { - return { - eyebrow: 'Image Registration', - title: 'Scene 이미지 등록', - description: - 'Space에 들어온 사용자가 선택하는 배경 이미지 세트를 등록합니다. cardFile과 stageFile은 최초 생성 시 필수입니다.', - accent: 'sky', - statTitle: 'Image Pipeline', - statValue: 'Scene Assets', - statHint: 'R2 / Manifest Sync', - }; - } - - return { - eyebrow: 'Audio Registration', - title: 'Sound 오디오 등록', - description: - 'Loop, preview, fallback 오디오를 등록합니다. loopFile은 최초 생성 시 필수이며 기본 볼륨과 duration 메타데이터를 함께 저장합니다.', - accent: 'emerald', - statTitle: 'Audio Pipeline', - statValue: 'Sound Assets', - statHint: 'Loop / Preview / Fallback', - }; + return copy.admin.views[view]; }; const formatResultSummary = (uploadResult: UploadResult | null) => { if (!uploadResult) { - return '아직 업로드 작업이 없습니다.'; + return copy.admin.inspector.noUploadSummary; } if (uploadResult.type === 'scene') { - return `${uploadResult.payload.sceneId} 이미지 세트가 최신 버전 ${uploadResult.payload.assetVersion}로 반영되었습니다.`; + return copy.admin.messages.sceneSummary(uploadResult.payload.sceneId, uploadResult.payload.assetVersion); } - return `${uploadResult.payload.presetId} 오디오 세트가 최신 버전 ${uploadResult.payload.assetVersion}로 반영되었습니다.`; + return copy.admin.messages.soundSummary(uploadResult.payload.presetId, uploadResult.payload.assetVersion); }; export default function AdminPage() { const [session, setSession] = useState(null); const [activeView, setActiveView] = useState('scene'); - const [loginId, setLoginId] = useState('qwer1234'); - const [password, setPassword] = useState('qwer1234!'); + const [loginId, setLoginId] = useState(copy.admin.defaultLoginId); + const [password, setPassword] = useState(copy.admin.defaultPassword); const [loginError, setLoginError] = useState(null); const [loginPending, setLoginPending] = useState(false); const [scenePending, setScenePending] = useState(false); @@ -142,13 +108,13 @@ export default function AdminPage() { }); if (response.user?.grade !== 'ADMIN') { - throw new Error('ADMIN 권한이 없는 계정입니다.'); + throw new Error(copy.admin.messages.nonAdmin); } setSession(response); storeSession(response); } catch (error) { - setLoginError(error instanceof Error ? error.message : '로그인에 실패했습니다.'); + setLoginError(error instanceof Error ? error.message : copy.admin.messages.loginFailed); } finally { setLoginPending(false); } @@ -166,7 +132,7 @@ export default function AdminPage() { const handleSceneUpload = async (event: FormEvent) => { event.preventDefault(); if (!session?.accessToken) { - setSceneMessage('먼저 관리자 로그인을 해주세요.'); + setSceneMessage(copy.admin.messages.loginRequired); return; } @@ -179,7 +145,7 @@ export default function AdminPage() { const sceneId = String(source.get('sceneId') ?? '').trim(); if (!sceneId) { - throw new Error('sceneId를 입력해주세요.'); + throw new Error(copy.admin.messages.sceneIdRequired); } const formData = new FormData(); @@ -199,10 +165,10 @@ export default function AdminPage() { const response = await adminApi.uploadScene(sceneId, formData, session.accessToken); setUploadResult({ type: 'scene', payload: response }); - setSceneMessage(`scene "${sceneId}" 업로드가 완료되었습니다.`); + setSceneMessage(copy.admin.messages.sceneUploadDone(sceneId)); form.reset(); } catch (error) { - setSceneMessage(error instanceof Error ? error.message : 'scene 업로드에 실패했습니다.'); + setSceneMessage(error instanceof Error ? error.message : copy.admin.messages.sceneUploadFailed); } finally { setScenePending(false); } @@ -211,7 +177,7 @@ export default function AdminPage() { const handleSoundUpload = async (event: FormEvent) => { event.preventDefault(); if (!session?.accessToken) { - setSoundMessage('먼저 관리자 로그인을 해주세요.'); + setSoundMessage(copy.admin.messages.loginRequired); return; } @@ -224,7 +190,7 @@ export default function AdminPage() { const presetId = String(source.get('presetId') ?? '').trim(); if (!presetId) { - throw new Error('presetId를 입력해주세요.'); + throw new Error(copy.admin.messages.presetIdRequired); } const formData = new FormData(); @@ -244,10 +210,10 @@ export default function AdminPage() { const response = await adminApi.uploadSound(presetId, formData, session.accessToken); setUploadResult({ type: 'sound', payload: response }); - setSoundMessage(`sound "${presetId}" 업로드가 완료되었습니다.`); + setSoundMessage(copy.admin.messages.soundUploadDone(presetId)); form.reset(); } catch (error) { - setSoundMessage(error instanceof Error ? error.message : 'sound 업로드에 실패했습니다.'); + setSoundMessage(error instanceof Error ? error.message : copy.admin.messages.soundUploadFailed); } finally { setSoundPending(false); } @@ -267,15 +233,15 @@ export default function AdminPage() { V
-

VibeRoom

-

Admin Console

+

{copy.appName}

+

{copy.admin.consoleLabel}

- Media Operations + {copy.admin.mediaOperations}

{navItems.map((item, index) => ( @@ -296,36 +262,36 @@ export default function AdminPage() {
-

Access

-

Admin Only

+

{copy.admin.access}

+

{copy.admin.accessTitle}

- 로그인 후 업로드 토큰으로 scene 이미지와 sound 오디오를 바로 R2에 반영합니다. + {copy.admin.accessDescription}

- Local admin credentials + {copy.admin.localCredentialsHint}

-

Sign In

+

{copy.admin.signInEyebrow}

- 관리자 로그인 + {copy.admin.loginTitle}

- 관리자 권한이 확인되면 좌측 사이드바 기반 대시보드가 열립니다. + {copy.admin.loginDescription}

setLoginId(event.target.value)} autoComplete="username" - placeholder="qwer1234" + placeholder={copy.admin.defaultLoginId} />
setPassword(event.target.value)} autoComplete="current-password" - placeholder="qwer1234!" + placeholder={copy.admin.defaultPassword} />
{loginError ? ( @@ -356,7 +322,7 @@ export default function AdminPage() {
) : null} @@ -375,15 +341,15 @@ export default function AdminPage() { V
-

VibeRoom

-

Media Admin

+

{copy.appName}

+

{copy.admin.mediaAdminLabel}