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
225 changes: 194 additions & 31 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,190 @@
import { Routes, Route } from 'react-router-dom'
import { Box, Typography, Container, Button, Stack } from '@mui/material'
import { Box, Typography, Container, Card, CardContent, Grid, Button } from '@mui/material'
import {
RecordVoiceOver as InterviewIcon,
Headphones as OpicIcon,
Chat as FreetalkIcon,
Edit as WritingIcon,
} from '@mui/icons-material'
import MainLayout from './layouts/MainLayout'

// 임시 대시보드 페이지
function Dashboard() {
const learningModes = [
{
id: 'interview',
title: '면접 시뮬레이션',
description: 'AI 면접관과 실전처럼 연습하세요',
icon: InterviewIcon,
color: '#0124ac',
path: '/interview',
},
{
id: 'opic',
title: 'OPIC 연습',
description: '레벨별 맞춤 문제로 실력 향상',
icon: OpicIcon,
color: '#2196f3',
path: '/opic',
},
{
id: 'freetalk',
title: '프리토킹',
description: 'AI와 자유롭게 영어로 대화',
icon: FreetalkIcon,
color: '#4caf50',
path: '/freetalk',
},
{
id: 'writing',
title: '작문 연습',
description: '문법 교정과 표현 피드백',
icon: WritingIcon,
color: '#ff9800',
path: '/writing',
},
]

function Home() {
return (
<Container maxWidth="lg">
<Box sx={{ mt: 4, textAlign: 'center' }}>
<Typography variant="h3" component="h1" gutterBottom>
Welcome to FE Repository
<Box sx={{ mb: 4 }}>
<Typography variant="h4" fontWeight={700} gutterBottom>
안녕하세요! 👋
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
React + Vite + MUI 프로젝트가 준비되었습니다.
<Typography variant="body1" color="text.secondary">
오늘은 어떤 학습을 해볼까요?
</Typography>
</Box>

<Grid container spacing={3}>
{learningModes.map((mode) => {
const Icon = mode.icon
return (
<Grid item xs={12} sm={6} md={3} key={mode.id}>
<Card
sx={{
height: '100%',
cursor: 'pointer',
transition: 'transform 0.2s, box-shadow 0.2s',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: 4,
},
}}
>
<CardContent sx={{ textAlign: 'center', py: 4 }}>
<Box
sx={{
width: 64,
height: 64,
borderRadius: '50%',
backgroundColor: `${mode.color}15`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto 16px',
}}
>
<Icon sx={{ fontSize: 32, color: mode.color }} />
</Box>
<Typography variant="h6" fontWeight={600} gutterBottom>
{mode.title}
</Typography>
<Typography variant="body2" color="text.secondary">
{mode.description}
</Typography>
</CardContent>
</Card>
</Grid>
)
})}
</Grid>

<Stack direction="row" spacing={2} justifyContent="center" sx={{ mb: 3 }}>
<Button variant="contained" color="primary" size="large">
Primary 버튼
</Button>
<Button variant="outlined" color="primary" size="large">
Outlined 버튼
</Button>
<Button variant="contained" color="secondary" size="large">
Secondary 버튼
</Button>
</Stack>

<Box sx={{
mt: 4,
p: 3,
bgcolor: 'primary.main',
color: 'white',
borderRadius: 2
}}>
<Typography variant="h6">
Primary Color: #0124ac
</Typography>
</Box>
{/* 최근 학습 */}
<Box sx={{ mt: 6 }}>
<Typography variant="h5" fontWeight={600} gutterBottom>
최근 학습
</Typography>
<Card>
<CardContent>
<Typography color="text.secondary" textAlign="center" py={4}>
아직 학습 기록이 없습니다. 학습을 시작해보세요!
</Typography>
</CardContent>
</Card>
</Box>
</Container>
)
}

// 임시 페이지들
function InterviewPage() {
return (
<Container>
<Typography variant="h4">면접 시뮬레이션</Typography>
<Typography color="text.secondary">AI 면접관과 실전 연습</Typography>
</Container>
)
}

function OpicPage() {
return (
<Container>
<Typography variant="h4">OPIC 연습</Typography>
<Typography color="text.secondary">레벨별 맞춤 연습</Typography>
</Container>
)
}

function FreetalkPage() {
return (
<Container>
<Typography variant="h4">프리토킹</Typography>
<Typography color="text.secondary">AI와 자유로운 대화</Typography>
</Container>
)
}

function WritingPage() {
return (
<Container>
<Typography variant="h4">작문 연습</Typography>
<Typography color="text.secondary">문법 교정 & 피드백</Typography>
</Container>
)
}

function ReportsPage() {
return (
<Container>
<Typography variant="h4">내 리포트</Typography>
<Typography color="text.secondary">학습 결과 분석</Typography>
</Container>
)
}

function SettingsPage() {
return (
<Container>
<Typography variant="h4">설정</Typography>
<Typography color="text.secondary">계정 및 앱 설정</Typography>
</Container>
)
}

function NotFound() {
return (
<Container>
<Box textAlign="center" py={8}>
<Typography variant="h1" fontWeight={700} color="primary">
404
</Typography>
<Typography variant="h5" gutterBottom>
페이지를 찾을 수 없습니다
</Typography>
<Button variant="contained" href="/" sx={{ mt: 2 }}>
홈으로 돌아가기
</Button>
</Box>
</Container>
)
Expand All @@ -43,7 +193,20 @@ function Home() {
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
{/* MainLayout 적용 라우트 */}
<Route element={<MainLayout />}>
<Route path="/" element={<Dashboard />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/interview" element={<InterviewPage />} />
<Route path="/opic" element={<OpicPage />} />
<Route path="/freetalk" element={<FreetalkPage />} />
<Route path="/writing" element={<WritingPage />} />
<Route path="/reports" element={<ReportsPage />} />
<Route path="/settings" element={<SettingsPage />} />
</Route>

{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
)
}
Expand Down
79 changes: 79 additions & 0 deletions src/contexts/ThemeContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createContext, useContext, useState, useEffect, useMemo } from 'react'
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles'
import { CssBaseline } from '@mui/material'
import { lightTheme, darkTheme } from '../theme/theme'

const ThemeContext = createContext({
mode: 'light',
toggleTheme: () => {},
setMode: () => {},
})

export const useThemeMode = () => {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useThemeMode must be used within ThemeProvider')
}
return context
}

export const ThemeProvider = ({ children }) => {
// localStorage에서 테마 모드 불러오기 (시스템 설정 기본값)
const [mode, setMode] = useState(() => {
const savedMode = localStorage.getItem('themeMode')
if (savedMode) return savedMode

// 시스템 다크모드 감지
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
return 'light'
})

// 시스템 테마 변경 감지
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = (e) => {
const savedMode = localStorage.getItem('themeMode')
if (!savedMode) {
setMode(e.matches ? 'dark' : 'light')
}
}

mediaQuery.addEventListener('change', handleChange)
return () => mediaQuery.removeEventListener('change', handleChange)
}, [])

// 테마 변경 시 localStorage 저장
useEffect(() => {
localStorage.setItem('themeMode', mode)
// body 클래스 추가 (CSS 변수 등에서 활용)
document.body.classList.remove('light-mode', 'dark-mode')
document.body.classList.add(`${mode}-mode`)
}, [mode])

const toggleTheme = () => {
setMode((prev) => (prev === 'light' ? 'dark' : 'light'))
}

const theme = useMemo(() => {
return mode === 'dark' ? darkTheme : lightTheme
}, [mode])

const contextValue = useMemo(() => ({
mode,
toggleTheme,
setMode,
}), [mode])

return (
<ThemeContext.Provider value={contextValue}>
<MuiThemeProvider theme={theme}>
<CssBaseline />
{children}
</MuiThemeProvider>
</ThemeContext.Provider>
)
}

export default ThemeContext
71 changes: 71 additions & 0 deletions src/layouts/MainLayout/Footer/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Box, Typography, Link, Container, Divider } from '@mui/material'

const Footer = () => {
return (
<Box
component="footer"
sx={{
py: 3,
px: 2,
mt: 'auto',
backgroundColor: 'background.paper',
borderTop: 1,
borderColor: 'divider',
}}
>
<Container maxWidth="lg">
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
justifyContent: 'space-between',
alignItems: { xs: 'center', sm: 'flex-start' },
gap: 2,
}}
>
{/* 저작권 */}
<Typography variant="body2" color="text.secondary">
© 2026 AI Language Learning. All rights reserved.
</Typography>

{/* 링크 */}
<Box
sx={{
display: 'flex',
gap: 3,
flexWrap: 'wrap',
justifyContent: 'center',
}}
>
<Link
href="/terms"
color="text.secondary"
underline="hover"
variant="body2"
>
이용약관
</Link>
<Link
href="/privacy"
color="text.secondary"
underline="hover"
variant="body2"
>
개인정보처리방침
</Link>
<Link
href="/contact"
color="text.secondary"
underline="hover"
variant="body2"
>
고객센터
</Link>
</Box>
</Box>
</Container>
</Box>
)
}

export default Footer
Loading