From 0aa6db1b3bafda70c73c46e2892389ecb2299454 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 Aug 2025 11:35:05 +0900 Subject: [PATCH] feat: Complete Material-UI migration for all pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated Dashboard page to use Material-UI components - Updated SignupPage to use Material-UI with Google OAuth style - Fixed Material-UI icon import issues (replaced Activity with Timeline) - Updated CLAUDE.md to reflect UI framework migration from Lucide/Tailwind to Material-UI - All pages now follow consistent Google OAuth design pattern πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- CLAUDE.md | 5 +- oauth/frontend/src/pages/Dashboard.tsx | 426 ++++++++++------- oauth/frontend/src/pages/SignupPage.tsx | 608 ++++++++++++------------ 3 files changed, 580 insertions(+), 459 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 06c1bff..d09ce9d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,7 +25,10 @@ ### 기술 μŠ€νƒ - **API Gateway**: Apache APISIX 3.8.0 -- **Frontend**: React 18 + Vite + TypeScript + shadcn/ui + Tailwind CSS +- **Frontend**: React 18 + Vite + TypeScript + Material-UI (MUI) + - 이전: Lucide React + Tailwind CSS + - ν˜„μž¬: Material-UI (@mui/material, @emotion/react, @emotion/styled, @mui/icons-material) + - λ””μžμΈ: Google OAuth μŠ€νƒ€μΌ UI/UX - **Backend**: Python 3.11 + FastAPI + Motor (MongoDB async) - **Database**: MongoDB 7.0 - **Cache/Queue**: Redis 7 diff --git a/oauth/frontend/src/pages/Dashboard.tsx b/oauth/frontend/src/pages/Dashboard.tsx index b9e3811..ed647b8 100644 --- a/oauth/frontend/src/pages/Dashboard.tsx +++ b/oauth/frontend/src/pages/Dashboard.tsx @@ -1,31 +1,82 @@ import { useState } from 'react' +import { useNavigate } from 'react-router-dom' import { useAuth } from '../contexts/AuthContext' -import { - LayoutDashboard, - Users, - Settings, - LogOut, - ChevronDown, - Bell, - Search, +import { + Box, + Container, + Grid, + Card, + CardContent, + Typography, + Button, + AppBar, + Toolbar, + IconButton, + Avatar, Menu, - X, - Shield, - Key, - Activity, - Clock -} from 'lucide-react' + MenuItem, + Drawer, + List, + ListItem, + ListItemIcon, + ListItemText, + ListItemButton, + Divider, + Paper, + LinearProgress, + Chip, + InputBase, + Badge +} from '@mui/material' +import { + Dashboard as DashboardIcon, + Apps, + Person, + AdminPanelSettings, + Menu as MenuIcon, + Logout, + Settings, + TrendingUp, + Security, + Speed, + AccessTime, + Notifications, + Search, + VpnKey, + Timeline +} from '@mui/icons-material' const Dashboard = () => { + const [drawerOpen, setDrawerOpen] = useState(false) + const [anchorEl, setAnchorEl] = useState(null) const { user, logout } = useAuth() - const [isSidebarOpen, setIsSidebarOpen] = useState(true) - const [isProfileOpen, setIsProfileOpen] = useState(false) + const navigate = useNavigate() + + const handleLogout = async () => { + await logout() + navigate('/login') + } + + const handleProfileMenuOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget) + } + + const handleProfileMenuClose = () => { + setAnchorEl(null) + } + + const menuItems = [ + { text: 'λŒ€μ‹œλ³΄λ“œ', icon: , path: '/dashboard' }, + { text: 'μ• ν”Œλ¦¬μΌ€μ΄μ…˜', icon: , path: '/applications' }, + { text: 'ν”„λ‘œν•„ μ„€μ •', icon: , path: '/profile' }, + { text: 'κ΄€λ¦¬μž νŒ¨λ„', icon: , path: '/admin', adminOnly: true }, + ] const stats = [ - { title: 'ν™œμ„± μ„Έμ…˜', value: '24', icon: Users, change: '+12%' }, - { title: 'λ“±λ‘λœ μ•±', value: '8', icon: Key, change: '+2' }, - { title: '이번 달 둜그인', value: '1,429', icon: Activity, change: '+48%' }, - { title: '평균 μ‘λ‹΅μ‹œκ°„', value: '132ms', icon: Clock, change: '-12%' }, + { title: 'ν™œμ„± μ„Έμ…˜', value: '24', icon: , change: '+12%', color: '#1a73e8' }, + { title: 'λ“±λ‘λœ μ•±', value: '8', icon: , change: '+2', color: '#34a853' }, + { title: '이번 달 둜그인', value: '1,429', icon: , change: '+48%', color: '#fbbc04' }, + { title: '평균 μ‘λ‹΅μ‹œκ°„', value: '132ms', icon: , change: '-12%', color: '#ea4335' }, ] const recentActivities = [ @@ -36,154 +87,209 @@ const Dashboard = () => { ] return ( -
- {/* Sidebar */} - + + + + OAuth System + + + {/* Search Bar */} + + + + + + + + + + + + + + + + {user?.name?.[0] || user?.email?.[0]?.toUpperCase() || 'U'} + + + + { handleProfileMenuClose(); navigate('/profile'); }}> + + ν”„λ‘œν•„ μ„€μ • + + + + + λ‘œκ·Έμ•„μ›ƒ + + + + + + {/* Drawer */} + setDrawerOpen(false)} + > + + + + OAuth System + + + + {menuItems.map((item) => { + if (item.adminOnly && user?.role !== 'system_admin') return null + return ( + { + navigate(item.path) + setDrawerOpen(false) + }} + selected={location.pathname === item.path} + sx={{ + '&.Mui-selected': { + backgroundColor: '#e8f0fe', + color: '#1967d2', + '& .MuiListItemIcon-root': { + color: '#1967d2', + } + } + }} + > + {item.icon} + + + ) + })} + + + + + + + + + + {/* Main Content */} -
- {/* Header */} -
-
-
- -
- - -
-
- -
- - -
- - - {isProfileOpen && ( -
- - ν”„λ‘œν•„ μ„€μ • - -
- -
- )} -
-
-
-
- - {/* Dashboard Content */} -
-
-

λŒ€μ‹œλ³΄λ“œ

-

OAuth μ‹œμŠ€ν…œ ν˜„ν™©μ„ ν•œλˆˆμ— ν™•μΈν•˜μ„Έμš”

-
+ + + {/* Page Header */} + + + λŒ€μ‹œλ³΄λ“œ + + + OAuth μ‹œμŠ€ν…œ ν˜„ν™©μ„ ν•œλˆˆμ— ν™•μΈν•˜μ„Έμš” + + {/* Stats Grid */} -
+ {stats.map((stat) => ( -
-
-
- -
- - {stat.change} - -
-

{stat.value}

-

{stat.title}

-
+ + + + + + {stat.icon} + + + + + {stat.value} + + + {stat.title} + + + + ))} -
+ - {/* Recent Activities */} -
-
-

졜근 ν™œλ™

-
-
-
- {recentActivities.map((activity) => ( -
-
-

{activity.action}

-

{activity.app}

-
- {activity.time} -
- ))} -
-
-
-
-
-
+ {/* Recent Activity */} + + + + 졜근 ν™œλ™ + + + + {recentActivities.map((activity, index) => ( + + + + {activity.action} + + } + secondary={ + + + {activity.app} + + + β€’ {activity.time} + + + } + /> + + {index < recentActivities.length - 1 && } + + ))} + + + + + ) } diff --git a/oauth/frontend/src/pages/SignupPage.tsx b/oauth/frontend/src/pages/SignupPage.tsx index 35f93f0..9473c06 100644 --- a/oauth/frontend/src/pages/SignupPage.tsx +++ b/oauth/frontend/src/pages/SignupPage.tsx @@ -1,17 +1,30 @@ import { useState } from 'react' import { useNavigate } from 'react-router-dom' -import { - Eye, - EyeOff, - Loader2, - Shield, +import { + Box, + Container, + TextField, + Button, + Typography, + Link, + Paper, + IconButton, + InputAdornment, + Checkbox, + FormControlLabel, + LinearProgress, + Alert, + Stack +} from '@mui/material' +import { + Visibility, + VisibilityOff, + Person, + Email, Lock, - Mail, - ArrowRight, - User, - Building, - Check -} from 'lucide-react' + Business, + ArrowForward +} from '@mui/icons-material' const SignupPage = () => { const [formData, setFormData] = useState({ @@ -25,7 +38,6 @@ const SignupPage = () => { const [showPassword, setShowPassword] = useState(false) const [showConfirmPassword, setShowConfirmPassword] = useState(false) const [isLoading, setIsLoading] = useState(false) - const [focusedInput, setFocusedInput] = useState(null) const [passwordStrength, setPasswordStrength] = useState(0) const navigate = useNavigate() @@ -89,12 +101,12 @@ const SignupPage = () => { const getPasswordStrengthColor = () => { switch(passwordStrength) { - case 0: return 'bg-gray-400' - case 1: return 'bg-red-500' - case 2: return 'bg-yellow-500' - case 3: return 'bg-blue-500' - case 4: return 'bg-green-500' - default: return 'bg-gray-400' + case 0: return '#bdbdbd' + case 1: return '#f44336' + case 2: return '#ff9800' + case 3: return '#2196f3' + case 4: return '#4caf50' + default: return '#bdbdbd' } } @@ -110,293 +122,293 @@ const SignupPage = () => { } return ( -
- {/* Animated background elements */} -
-
-
-
-
+ + + + {/* Logo and Title */} + + + AiMond + + + AiMond 계정 λ§Œλ“€κΈ° + + + κ³„μ†ν•˜λ €λ©΄ AiMond둜 이동 + + - {/* Grid pattern overlay */} -
- - {/* Main content */} -
-
- - {/* Left side - Branding */} -
-
-
-
- -
-

- AiMond Authorization -

-
-

- μƒˆλ‘œμš΄ 계정을 μƒμ„±ν•˜κ³  μ‹œμž‘ν•˜μ„Έμš” -

-
- -
-
-
- -
-
-

λΉ λ₯Έ κ°€μž… 절차

-

λͺ‡ κ°€μ§€ μ •λ³΄λ§ŒμœΌλ‘œ κ°„λ‹¨ν•˜κ²Œ κ°€μž… μ™„λ£Œ

-
-
- -
-
- -
-
-

μ—”ν„°ν”„λΌμ΄μ¦ˆ λ³΄μ•ˆ

-

졜고 μˆ˜μ€€μ˜ λ³΄μ•ˆμœΌλ‘œ 데이터 보호

-
-
- -
-
- -
-
-

쑰직 λ‹¨μœ„ 관리

-

νŒ€κ³Ό 쑰직을 효율적으둜 관리

-
-
-
-
- - {/* Right side - Signup Form */} -
-
- {/* Form Header */} -
-

Create Account

-

μƒˆλ‘œμš΄ 계정을 μƒμ„±ν•˜μ„Έμš”

-
- - {/* Signup Form */} -
- {/* Name Input */} -
- -
- setFocusedInput('name')} - onBlur={() => setFocusedInput(null)} - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" - placeholder="홍길동" - disabled={isLoading} - /> -
-
-
- - {/* Email Input */} -
- -
- setFocusedInput('email')} - onBlur={() => setFocusedInput(null)} - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" - placeholder="your@email.com" - disabled={isLoading} - /> -
-
-
- - {/* Organization Input */} -
- -
- setFocusedInput('organization')} - onBlur={() => setFocusedInput(null)} - className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" - placeholder="AiMond Inc." - disabled={isLoading} - /> -
-
-
- - {/* Password Input */} -
- -
- setFocusedInput('password')} - onBlur={() => setFocusedInput(null)} - className="w-full px-4 py-3 pr-12 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" - placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" - disabled={isLoading} - /> - -
-
- {formData.password && ( -
-
-
-
- {getPasswordStrengthText()} -
- )} -
- - {/* Confirm Password Input */} -
- -
- setFocusedInput('confirmPassword')} - onBlur={() => setFocusedInput(null)} - className="w-full px-4 py-3 pr-12 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" - placeholder="β€’β€’β€’β€’β€’β€’β€’β€’" - disabled={isLoading} - /> - -
-
-
- - {/* Terms Agreement */} -
- + + + + + + + ), + }} /> - -
+ - {/* Submit Button */} - -
+ + + + ), + }} + /> - {/* Sign in link */} -

- 이미 계정이 μžˆμœΌμ‹ κ°€μš”?{' '} - - λ‘œκ·ΈμΈν•˜κΈ° - -

-
+ + + + ), + }} + /> - {/* Security Badge */} -
- - 256-bit SSL μ•”ν˜Έν™”λ‘œ 보호됨 -
-
-
-
-
+ + + + ), + endAdornment: ( + + setShowPassword(!showPassword)} + edge="end" + size="small" + > + {showPassword ? : } + + + ), + }} + /> + + {formData.password && ( + + + + λΉ„λ°€λ²ˆν˜Έ 강도: {getPasswordStrengthText()} + + + )} + + + + + ), + endAdornment: ( + + setShowConfirmPassword(!showConfirmPassword)} + edge="end" + size="small" + > + {showConfirmPassword ? : } + + + ), + }} + /> + + + 문자, 숫자, 기호λ₯Ό μ‘°ν•©ν•˜μ—¬ 8자 μ΄μƒμ˜ λΉ„λ°€λ²ˆν˜Έλ₯Ό μ‚¬μš©ν•˜μ„Έμš” + + + + } + label={ + + μ„œλΉ„μŠ€ μ•½κ΄€ 및{' '} + κ°œμΈμ •λ³΄μ²˜λ¦¬λ°©μΉ¨μ— λ™μ˜ν•©λ‹ˆλ‹€ + + } + /> + + + + λŒ€μ‹  둜그인 + + + + + + + + {/* Footer Text */} + + + ν•˜λ‚˜μ˜ AiMond κ³„μ •μœΌλ‘œ λͺ¨λ“  AiMond μ„œλΉ„μŠ€λ₯Ό μ΄μš©ν•˜μ‹€ 수 μžˆμŠ΅λ‹ˆλ‹€ + + + + + + {/* Footer */} + + + + ν•œκ΅­μ–΄ + + + 도움말 + + + κ°œμΈμ •λ³΄μ²˜λ¦¬λ°©μΉ¨ + + + μ•½κ΄€ + + + + + + ) }