Skip to content
15 changes: 14 additions & 1 deletion src/app/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import './App.css';
import LoginSelect from '#pages/login-select';
import LoginRedirect from '#pages/login-redirect';
import MakeQuiz from '#pages/make-quiz';
import Board from '#pages/board';
import BoardDetail from '#pages/board-detail';
import BoardWrite from '#pages/board-write';
import PrivacyPolicy from '#pages/privacy-policy';
import QuizExplanation from '#pages/quiz-explanation';
import QuizHistory from '#pages/quiz-history';
Expand All @@ -14,12 +17,13 @@ import { I18nProvider, useLanguageSwitcher, useTranslation } from 'i18nexus';
import { translations } from '#shared/i18n';
import PageViewTracker from '#app/ui/PageViewTracker';
import { useInitGA } from '#app/model/useInitGA';
import { configureAuth } from '#shared/api';
import { useAuthStore, authService } from '#entities/auth';

// Google Analytics 측정 ID (실제 GA4 측정 ID로 교체 필요)
const GA_MEASUREMENT_ID = import.meta.env.VITE_GA_MEASUREMENT_ID;

const SUPPORTED_LANGUAGES = new Set(['ko', 'en']);

const SEO_CONFIG = {
ko: {
'/': {
Expand Down Expand Up @@ -364,6 +368,11 @@ const updateJsonLd = (id, data) => {
}
element.textContent = JSON.stringify(data);
};
configureAuth({
getAccessToken: () => useAuthStore.getState().accessToken,
clearAuth: () => useAuthStore.getState().clearAuth(),
refreshAuthToken: () => authService.refresh(),
});

const SeoMetaSync = () => {
const { currentLanguage } = useTranslation();
Expand Down Expand Up @@ -474,6 +483,10 @@ const App = () => {
<Route path="/result/:problemSetId" element={<QuizResult />} />
<Route path="/explanation/:problemSetId" element={<QuizExplanation />} />
<Route path="/history" element={<QuizHistory />} />
<Route path="/board" element={<Board />} />
<Route path="/board/:boardId" element={<BoardDetail />} />
<Route path="/board/write" element={<BoardWrite />} />
<Route path="/board/edit/:boardId" element={<BoardWrite />} />
<Route path="/help" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
Expand Down
291 changes: 291 additions & 0 deletions src/pages/board-detail/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@

.board-container-wrapper {
width: 100%;
background-color: #f9fafb;
min-height: 100vh;
display: flex;
flex-direction: column;
}

.board-detail-container {
width: 100%;
max-width: 800px;
margin: 40px auto;
padding: 0 20px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

.loading-wrapper {
text-align: center;
padding: 100px;
color: #6b7280;
font-size: 1.1rem;
}

.detail-card {
background: #ffffff;
border-radius: 1rem;
padding: 40px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 24px;
box-sizing: border-box;
}

.detail-header {
border-bottom: 1px solid #e5e7eb;
padding-bottom: 20px;
margin-bottom: 30px;
}

.detail-category {
color: #6366f1;
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 8px;
}

.detail-title {
font-size: 1.75rem;
color: #111827;
margin: 0 0 16px 0;
line-height: 1.3;
font-weight: 800;
}

.detail-meta {
font-size: 0.95rem;
color: #6b7280;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}

.meta-divider { color: #e5e7eb; }

.detail-content {
min-height: 200px;
color: #374151;
line-height: 1.8;
font-size: 1.05rem;
white-space: pre-wrap;
}

.replies-section {
margin-top: 32px;
margin-bottom: 40px;
}

.replies-title {
font-size: 1.25rem;
font-weight: 700;
color: #111827;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
}

.replies-count {
color: #6366f1;
font-size: 1.1rem;
}

.replies-list {
display: flex;
flex-direction: column;
gap: 16px;
}

.reply-item {
background: #ffffff;
border: 1px solid #e5e7eb;
border-radius: 1rem;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
position: relative;
}

.reply-item::before {
content: "↳";
position: absolute;
left: -20px;
top: 18px;
color: #d1d5db;
font-size: 1.2rem;
font-weight: bold;
}

.reply-content {
color: #4b5563;
line-height: 1.6;
font-size: 0.95rem;
white-space: pre-wrap;
}

.empty-reply {
text-align: center;
padding: 30px 0;
color: #9ca3af;
background: #ffffff;
border-radius: 1rem;
border: 1px dashed #d1d5db;
font-size: 0.95rem;
}

.detail-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 30px;
padding-bottom: 40px;
gap: 16px;
flex-wrap: wrap;
}

.left-actions, .author-actions {
display: flex;
gap: 12px;
}

.btn-back, .btn-write-new, .btn-edit, .btn-delete {
padding: 12px 24px;
border-radius: 0.75rem;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
border: none;
transition: all 0.2s;
box-sizing: border-box;
text-align: center;
white-space: nowrap;
}

.btn-back { background-color: #ffffff; color: #4b5563; border: 1px solid #e5e7eb; }
.btn-back:hover { background-color: #f3f4f6; }

.btn-write-new { background-color: #ffffff; color: #6366f1; border: 1px solid #6366f1; }
.btn-write-new:hover { background-color: #e0e7ff; }

.btn-edit { background-color: #3b82f6; color: white; }
.btn-edit:hover { background-color: #2563eb; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); }

.btn-delete { background-color: #ef4444; color: white; }
.btn-delete:hover { background-color: #dc2626; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3); }


@media screen and (max-width: 768px) {
.board-detail-container {
margin: 20px auto;
padding: 0 16px;
}

.detail-card {
padding: 24px 20px;
}

.detail-title {
font-size: 1.4rem;
}

.detail-meta {
font-size: 0.85rem;
gap: 8px;
}

.meta-divider {
display: none;
}


.detail-actions {
flex-direction: column;
gap: 12px;
}

.left-actions, .author-actions {
width: 100%;
justify-content: space-between;
gap: 12px;
}

.btn-back, .btn-write-new, .btn-edit, .btn-delete {
flex: 1;
padding: 12px 0;
font-size: 0.9rem;
}
}

.admin-reply-form {
margin-top: 24px;
background: #f8fafc; /* 기존 카드보다 살짝 어두운 배경으로 구분감 부여 */
border: 1px solid #e5e7eb;
border-radius: 1rem;
padding: 24px;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.02);
}

.admin-reply-title {
margin: 0 0 16px 0;
font-size: 1.1rem;
font-weight: 700;
color: #374151;
display: flex;
align-items: center;
gap: 8px;
}

.admin-reply-title::before {
content: "🛡️"; /* 관리자 느낌을 주는 아이콘 */
font-size: 1.2rem;
}

.admin-reply-textarea {
width: 100%;
padding: 16px;
border-radius: 0.75rem;
border: 1px solid #d1d5db;
font-size: 0.95rem;
font-family: inherit;
line-height: 1.6;
color: #1f2937;
resize: vertical;
box-sizing: border-box;
transition: border-color 0.2s, box-shadow 0.2s;
background-color: #ffffff;
}

.admin-reply-textarea:focus {
outline: none;
border-color: #6366f1; /* 메인 테마 색상으로 포커스 */
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}

.admin-reply-actions {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}

.btn-reply-submit {
padding: 12px 28px;
background-color: #6366f1; /* 메인 테마 색상 */
color: #ffffff;
border: none;
border-radius: 0.75rem;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.2s;
}

.btn-reply-submit:hover {
background-color: #4f46e5;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
}

.btn-reply-submit:active {
transform: translateY(0);
}
Loading