Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 117 additions & 26 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,140 @@
@tailwind components;
@tailwind utilities;

/* ============================================================
Pretendard 폰트 (한국어 최적화)
CDN 방식 - next/font 대신 사용 가능
또는 layout.tsx에서 next/font/local로 로드해도 됩니다.
============================================================ */
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css');

/* ============================================================
CSS 변수 (라이트 모드)
============================================================ */
:root {
--background: #ffffff;
--foreground: #171717;
/* 페이지 배경 - 크림톤 유지 */
--color-page-bg: #faf9f6;

/* 스크롤바 */
--scrollbar-thumb: #e4e4e7;
}

/* ============================================================
다크모드 CSS 변수
============================================================ */
.dark {
--color-page-bg: #0f0f10;
--scrollbar-thumb: #3f3f46;
}

/* ============================================================
Base Styles
============================================================ */
*,
*::before,
*::after {
box-sizing: border-box;
padding: 0;
margin: 0;
}

html {
/* 부드러운 스크롤 */
scroll-behavior: smooth;
/* 다크모드 색상 테마 */
color-scheme: light;
}

@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
html.dark {
color-scheme: dark;
}

body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
overflow-y: scroll;
-ms-overflow-style: none;
font-family:
'Pretendard Variable',
-apple-system,
BlinkMacSystemFont,
system-ui,
sans-serif;
background-color: var(--color-page-bg);
color: #18181b; /* label.DEFAULT */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
overflow-y: scroll;
/* 단어 단위 줄바꿈 (한국어) */
word-break: keep-all;
overflow-wrap: break-word;
}

body::-webkit-scrollbar {
display: none;
}

* {
box-sizing: border-box;
padding: 0;
margin: 0;
.dark body {
color: #fafafa; /* label.invert */
}

a {
color: inherit;
text-decoration: none;
}

@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}

li {
list-style-type: none;
}

/* ============================================================
스크롤바 커스텀 (웹킷)
============================================================ */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}

::-webkit-scrollbar-track {
background: transparent;
}

::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 9999px;
}

::-webkit-scrollbar-thumb:hover {
background: #a1a1aa;
}

/* Firefox */
* {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) transparent;
}

/* ============================================================
선택 영역 색상
============================================================ */
::selection {
background-color: #bfdbfe; /* tint-muted */
color: #1e3a5f;
}

/* ============================================================
포커스 스타일 (키보드 접근성)
============================================================ */
:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
border-radius: 4px;
}

/* ============================================================
공통 유틸리티
============================================================ */

/* 바텀시트/모달 열릴 때 body 스크롤 막기 */
body.overflow-hidden {
overflow: hidden;
}

/* 이미지 드래그 방지 */
img {
-webkit-user-drag: none;
user-select: none;
}
33 changes: 12 additions & 21 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import type { Metadata, Viewport } from 'next';
import './globals.css';
import { Toaster } from 'sonner';
import Header from '@/components/Header';
import Navbar from '@/components/Navbar';

const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
});

const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
});

export const metadata: Metadata = {
title: 'Readiary | 당신의 독서 일기',
description: '하루하루의 독서를 기록하고, 친구들과 공유하세요.',
manifest: '/manifest.json',
};

export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
themeColor: '#FAF9F6',
};

export default function RootLayout({
children,
}: Readonly<{
Expand All @@ -31,17 +26,13 @@ export default function RootLayout({
<head>
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/icons/icon-192x192-v2.png" />
<meta name="theme-color" content="#0f172a" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="apple-touch-icon" href="/icons/icon-512x512-v2.png" />
<link rel="icon" href="favicon.ico" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} overflow-x-hidden bg-white dark:bg-gray-900`}
>
<body className="overflow-x-hidden bg-[var(--color-page-bg)] dark:bg-dark-page">
<Header />
<Navbar />
<main className="flex-1 max-w-screen-md w-full mx-auto px-4 pt-[4rem] pb-[4.75rem] md:pt-[6rem] md:pb-[4rem]">
Expand All @@ -54,7 +45,7 @@ export default function RootLayout({
closeButton
toastOptions={{
className:
'text-sm font-sans text-label dark:text-white bg-background dark:bg-darkbg rounded-md shadow-md border border-gray-200 dark:border-gray-700 px-4 py-3',
'text-sm font-sans text-label dark:text-label-invert bg-surface dark:bg-dark-surface rounded-md shadow-card-md border border-border dark:border-dark-border px-4 py-3',
}}
/>
</body>
Expand Down
25 changes: 9 additions & 16 deletions app/onboarding/_components/OnboardingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,13 @@ export default function OnboardingForm() {
const [nickname, setNickname] = useState('');
const [bio, setBio] = useState('');
const [loading, setLoading] = useState(false);

const router = useRouter();

const handleSubmit = async () => {
setLoading(true);

if (!/^[a-zA-Z0-9_]+$/.test(nickname)) {
const errorMessage = '닉네임은 영어 알파벳과 숫자, 언더스코어(_)만 사용할 수 있습니다.';
toast.error(errorMessage);
toast.error('닉네임은 영어 알파벳과 숫자, 언더스코어(_)만 사용할 수 있습니다.');
setLoading(false);
return;
}
Expand Down Expand Up @@ -52,20 +50,16 @@ export default function OnboardingForm() {
tag = generateRandomTag();
tries++;
} else if (res.status === 409) {
const errorMessage = result.error || '이미 프로필이 존재합니다.';
toast.error(errorMessage);
toast.error(result.error || '이미 프로필이 존재합니다.');
router.push('/protected/dashboard');
return;
} else {
const errorMessage = result.error || '프로필 등록 중 오류가 발생했습니다.';
toast.error(errorMessage);
toast.error(result.error || '프로필 등록 중 오류가 발생했습니다.');
setLoading(false);
return;
}
}

const errorMessage = '중복 태그가 너무 많습니다. 닉네임을 바꿔보세요.';
toast.error(errorMessage);
toast.error('중복 태그가 너무 많습니다. 닉네임을 바꿔보세요.');
} catch (error) {
toast.error('예기치 않은 오류가 발생했습니다. 나중에 다시 시도해주세요.');
console.error(error);
Expand All @@ -77,7 +71,7 @@ export default function OnboardingForm() {
return (
<div className="flex items-center justify-center">
<div className="w-full space-y-4">
<h1 className="text-xl font-semibold text-center text-gray-900 dark:text-white">
<h1 className="text-xl font-semibold text-center text-label dark:text-label-invert">
프로필 설정
</h1>
<AnimatedSection>
Expand All @@ -87,17 +81,17 @@ export default function OnboardingForm() {
placeholder="이름"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full border p-2 rounded bg-white dark:bg-gray-800 dark:text-white"
className="w-full border p-2 rounded bg-surface dark:bg-dark-surface dark:text-label-invert"
/>
<FormLabel htmlFor="nickname">닉네임</FormLabel>
<Input
id="nickname"
placeholder="닉네임"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
className="w-full border p-2 rounded bg-white dark:bg-gray-800 dark:text-white"
className="w-full border p-2 rounded bg-surface dark:bg-dark-surface dark:text-label-invert"
/>
<p className="mt-1 text-sm text-muted-foreground">
<p className="mt-1 text-sm text-label-muted">
닉네임은 영어 알파벳과 숫자, 언더스코어(_)만 사용할 수 있습니다.
</p>
<FormLabel htmlFor="bio">자기소개</FormLabel>
Expand All @@ -106,9 +100,8 @@ export default function OnboardingForm() {
placeholder="자기소개 (선택)"
value={bio}
onChange={(e) => setBio(e.target.value)}
className="w-full border p-2 rounded bg-white dark:bg-gray-800 dark:text-white resize-none"
className="w-full border p-2 rounded bg-surface dark:bg-dark-surface dark:text-label-invert resize-none"
/>

<Button className="w-full" onClick={handleSubmit} disabled={loading} variant="primary">
{loading ? '등록 중...' : '프로필 등록하기'}
</Button>
Expand Down
13 changes: 7 additions & 6 deletions app/protected/dashboard/_components/InProgressBooksSection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import Link from 'next/link';

import { MyBook } from '@/types/book';
import Card from '@/components/ui/Card';

Expand All @@ -22,20 +21,22 @@ export function InProgressBooksSection({ myBooks }: Props) {

return (
<section className="mb-6 space-y-4">
<h2 className="text-section-title text-label dark:text-white">📚 진행 중인 책</h2>
<h2 className="text-section-title text-label dark:text-label-invert">📚 진행 중인 책</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{books.map((book, idx) => (
<Link key={idx} href={`/protected/books/${book.book_id}`}>
<Card hoverable>
<h3 className="text-base font-semibold text-label dark:text-white">{book.title}</h3>
<h3 className="text-base font-semibold text-label dark:text-label-invert">
{book.title}
</h3>
{book.author && (
<p className="text-sm text-secondary dark:text-gray-400">{book.author}</p>
<p className="text-sm text-label-sub dark:text-label-muted">{book.author}</p>
)}
<p className="mt-2 text-sm text-gray-600 dark:text-gray-400">
<p className="mt-2 text-sm text-label-sub dark:text-label-muted">
📈 진행률: {book.progress}%
</p>
{book.started_at && (
<p className="text-xs text-gray-400 dark:text-gray-500">
<p className="text-xs text-label-muted dark:text-label-muted">
등록일: {new Date(book.started_at).toLocaleDateString()}
</p>
)}
Expand Down
Loading
Loading