-
Notifications
You must be signed in to change notification settings - Fork 0
[FE] 메인 페이지 UI #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
[FE] 메인 페이지 UI #75
Changes from 8 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
1d8d085
✨feat: 메인 페이지 구성 #68
lnxhigh fab1f43
♻️refactor: ActionCard 컴포넌트 도입
lnxhigh e4db99c
✨feat: 방 생성 및 방 번호로 입장을 위한 Dialog 작성
lnxhigh a88fe4d
Merge branch 'dev' of https://github.com/boostcampwm2025/web08-JAMsta…
lnxhigh bea6926
✨feat: 카드를 눌러서 모달을 띄우는 것이 아닌 카드 안에 내용을 넣도록 디자인 수정
lnxhigh 8af60e0
✨feat: ActionCard 와 FeatureCard Section 분리
lnxhigh 8823718
✨feat: Create Quick Start Room, Join Room API 연결 #68
lnxhigh 75ddd2c
📝docs: 메인 페이지 layout 관련 문서 추가
lnxhigh 01b5182
🐛fix: App.tsx 의 RoomPage 라우팅 경로 수정
lnxhigh 2f1602f
Merge branch 'dev' into feat/#68
lnxhigh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { Hero } from "./sections/Hero"; | ||
| import { ActionCards } from "./sections/ActionCards"; | ||
| import { FeatureCards } from "./sections/FeatureCards"; | ||
|
|
||
| export default function MainPage() { | ||
| return ( | ||
| <div className="min-h-screen flex flex-col items-center justify-center p-4 relative overflow-hidden"> | ||
| <div className="relative z-10 w-full max-w-4xl"> | ||
| <Hero /> | ||
| <ActionCards /> | ||
| <FeatureCards /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import type { LucideIcon } from "lucide-react"; | ||
| import { | ||
| Card, | ||
| CardContent, | ||
| CardDescription, | ||
| CardHeader, | ||
| CardTitle, | ||
| } from "@/shared/ui/card"; | ||
| import { cardColorSchemes } from "../constants/card-color-schemes"; | ||
|
|
||
| interface ActionCardProps { | ||
| icon: LucideIcon; | ||
| title: string; | ||
| description: string; | ||
| colorKey: string; | ||
| children?: React.ReactNode; | ||
| } | ||
|
|
||
| export function ActionCard({ | ||
| icon: Icon, | ||
| title, | ||
| description, | ||
| colorKey, | ||
| children, | ||
| }: ActionCardProps) { | ||
| const colors = cardColorSchemes[colorKey]; | ||
|
|
||
| return ( | ||
| <Card | ||
| className={`bg-gray-50 border-gray-200 shadow-md ${colors.hoverBorderColor} transition-colors duration-200`} | ||
| > | ||
| <CardHeader className="pb-4 flex flex-col items-center text-center gap-3"> | ||
| <div | ||
| className={`${colors.iconBg} border ${colors.borderColor} w-12 h-12 rounded-full flex items-center justify-center`} | ||
| > | ||
| <Icon className={`h-6 w-6 ${colors.iconColor}`} /> | ||
| </div> | ||
|
|
||
| <CardTitle className="text-xl text-gray-800">{title}</CardTitle> | ||
| <CardDescription className="text-gray-600 text-sm"> | ||
| {description} | ||
| </CardDescription> | ||
| </CardHeader> | ||
| <CardContent className="flex flex-col items-center justify-center py-6"> | ||
| {children} | ||
| </CardContent> | ||
| </Card> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import type { LucideIcon } from "lucide-react"; | ||
| import { Card, CardDescription, CardHeader, CardTitle } from "@/shared/ui/card"; | ||
| import { cardColorSchemes } from "../constants/card-color-schemes"; | ||
|
|
||
| interface FeatureCardProps { | ||
| icon: LucideIcon; | ||
| title: string; | ||
| description: string; | ||
| colorKey: string; | ||
| } | ||
|
|
||
| export function FeatureCard({ | ||
| icon: Icon, | ||
| title, | ||
| description, | ||
| colorKey, | ||
| }: FeatureCardProps) { | ||
| const colors = cardColorSchemes[colorKey]; | ||
|
|
||
| return ( | ||
| <Card className={`border-transparent shadow-none ${colors.cardBg}`}> | ||
| <CardHeader className="flex flex-col items-center text-center gap-2"> | ||
| <div | ||
| className={`${colors.iconBg} border ${colors.borderColor} w-10 h-10 rounded-full flex items-center justify-center`} | ||
| > | ||
| <Icon className={`h-5 w-5 ${colors.iconColor}`} /> | ||
| </div> | ||
|
|
||
| <CardTitle className="text-base text-gray-800">{title}</CardTitle> | ||
| <CardDescription className="text-gray-600 text-xs font-mono"> | ||
| {description} | ||
| </CardDescription> | ||
| </CardHeader> | ||
| </Card> | ||
| ); | ||
| } |
130 changes: 130 additions & 0 deletions
130
apps/client/src/pages/home/components/RoomCodeInput.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| import { useRef } from "react"; | ||
| import type { KeyboardEvent, ClipboardEvent } from "react"; | ||
|
|
||
| export const ROOM_CODE_LENGTH = 6; | ||
|
|
||
| interface RoomCodeInputProps { | ||
| value: string[]; | ||
| onChange: (code: string[]) => void; | ||
| hasError?: boolean; | ||
| onSubmit?: () => void; | ||
| length?: number; | ||
| } | ||
|
|
||
| export function RoomCodeInput({ | ||
| value, | ||
| onChange, | ||
| hasError = false, | ||
| onSubmit, | ||
| length = ROOM_CODE_LENGTH, | ||
| }: RoomCodeInputProps) { | ||
| const inputRefs = useRef<(HTMLInputElement | null)[]>([]); | ||
|
|
||
| const handleChange = (index: number, inputValue: string) => { | ||
| if (inputValue !== "" && !isValidRoomCodeChar(inputValue)) return; | ||
|
|
||
| const newCode = [...value]; | ||
| newCode[index] = inputValue; | ||
| onChange(newCode); | ||
|
|
||
| // Move to next field after typing a character | ||
| if (inputValue !== "" && index < length - 1) { | ||
| inputRefs.current[index + 1]?.focus(); | ||
| } | ||
| }; | ||
|
|
||
| const handleKeyDown = (index: number, e: KeyboardEvent<HTMLInputElement>) => { | ||
| const inputValue = (e.target as HTMLInputElement).value; | ||
|
|
||
| // Backspace handling | ||
| // If current field is empty, move to previous field and clear it | ||
| if (e.key === "Backspace") { | ||
| if (inputValue === "" && index > 0) { | ||
| e.preventDefault(); | ||
| const newCode = [...value]; | ||
| newCode[index - 1] = ""; | ||
| onChange(newCode); | ||
| inputRefs.current[index - 1]?.focus(); | ||
| } | ||
| } | ||
|
|
||
| // Left arrow | ||
| if (e.key === "ArrowLeft" && index > 0) { | ||
| e.preventDefault(); | ||
| inputRefs.current[index - 1]?.focus(); | ||
| } | ||
|
|
||
| // Right arrow | ||
| if (e.key === "ArrowRight" && index < length - 1) { | ||
| e.preventDefault(); | ||
| inputRefs.current[index + 1]?.focus(); | ||
| } | ||
|
|
||
| // Enter key to submit | ||
| if (e.key === "Enter") { | ||
| const roomCode = value.join(""); | ||
| if (roomCode.length === length && onSubmit) { | ||
| onSubmit(); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| // Paste handling | ||
| const handlePaste = (index: number, e: ClipboardEvent<HTMLInputElement>) => { | ||
| e.preventDefault(); | ||
|
|
||
| const pastedData = e.clipboardData | ||
| .getData("text") | ||
| .toUpperCase() | ||
| .slice(0, length); | ||
|
|
||
| // Filter only alphanumeric characters | ||
| const validChars = pastedData | ||
| .split("") | ||
| .filter((char) => isValidRoomCodeChar(char)); | ||
|
|
||
| if (validChars.length === 0) return; | ||
|
|
||
| const newCode = [...value]; | ||
|
|
||
| // Fill from current index | ||
| validChars.forEach((char, i) => { | ||
| const targetIndex = index + i; | ||
| if (targetIndex < length) { | ||
| newCode[targetIndex] = char; | ||
| } | ||
| }); | ||
|
|
||
| onChange(newCode); | ||
|
|
||
| // Set new focus | ||
| const nextIndex = Math.min(index + validChars.length, length - 1); | ||
| inputRefs.current[nextIndex]?.focus(); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="flex gap-1 sm:gap-2 justify-center"> | ||
| {value.map((digit, index) => ( | ||
| <input | ||
| key={index} | ||
| ref={(el) => { | ||
| inputRefs.current[index] = el; | ||
| }} | ||
| type="text" | ||
| maxLength={1} | ||
| value={digit} | ||
| onChange={(e) => handleChange(index, e.target.value)} | ||
| onKeyDown={(e) => handleKeyDown(index, e)} | ||
| onPaste={(e) => handlePaste(index, e)} | ||
| className={`w-8 h-8 sm:w-10 sm:h-10 md:w-12 md:h-12 text-center text-base sm:text-xl md:text-2xl font-semibold font-mono border-2 ${ | ||
| hasError ? "border-red-500" : "border-gray-400" | ||
| } focus:border-gray-900 focus:outline-none transition-colors uppercase caret-transparent`} | ||
| /> | ||
| ))} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const isValidRoomCodeChar = (char: string): boolean => { | ||
| return /^[a-zA-Z0-9]$/.test(char); | ||
| }; |
61 changes: 61 additions & 0 deletions
61
apps/client/src/pages/home/constants/card-color-schemes.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| export interface CardColorScheme { | ||
| cardBg: string; | ||
| iconBg: string; | ||
| borderColor: string; | ||
| iconColor: string; | ||
| hoverCardBg: string; | ||
| hoverBorderColor: string; | ||
| } | ||
|
|
||
| const blue: CardColorScheme = { | ||
| cardBg: "bg-blue-50", | ||
| iconBg: "bg-blue-100", | ||
| borderColor: "border-blue-200", | ||
| iconColor: "text-blue-500", | ||
| hoverCardBg: "hover:bg-blue-50", | ||
| hoverBorderColor: "hover:border-blue-400", | ||
| }; | ||
|
|
||
| const green: CardColorScheme = { | ||
| cardBg: "bg-green-50", | ||
| iconBg: "bg-green-100", | ||
| borderColor: "border-green-200", | ||
| iconColor: "text-green-500", | ||
| hoverCardBg: "hover:bg-green-50", | ||
| hoverBorderColor: "hover:border-green-400", | ||
| }; | ||
|
|
||
| const purple: CardColorScheme = { | ||
| cardBg: "bg-purple-50", | ||
| iconBg: "bg-purple-100", | ||
| borderColor: "border-purple-200", | ||
| iconColor: "text-purple-500", | ||
| hoverCardBg: "hover:bg-purple-50", | ||
| hoverBorderColor: "hover:border-purple-400", | ||
| }; | ||
|
|
||
| const orange: CardColorScheme = { | ||
| cardBg: "bg-orange-50", | ||
| iconBg: "bg-orange-100", | ||
| borderColor: "border-orange-200", | ||
| iconColor: "text-orange-500", | ||
| hoverCardBg: "hover:bg-orange-50", | ||
| hoverBorderColor: "hover:border-orange-400", | ||
| }; | ||
|
|
||
| const red: CardColorScheme = { | ||
| cardBg: "bg-red-50", | ||
| iconBg: "bg-red-100", | ||
| borderColor: "border-red-200", | ||
| iconColor: "text-red-500", | ||
| hoverCardBg: "hover:bg-red-50", | ||
| hoverBorderColor: "hover:border-red-400", | ||
| }; | ||
|
|
||
| export const cardColorSchemes: Record<string, CardColorScheme> = { | ||
| blue, | ||
| green, | ||
| purple, | ||
| orange, | ||
| red, | ||
| } as const; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여기 path가 이제
/room/:roomCode이런식으로 되어야 하지 않나요?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러네요 이 부분 수정하겠습니다
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
클라이언트에서 room id 를 사용하고 있는 부분이 많은데 store 부분은 머지한 코드에서 다시 수정하는 게 나을 것 같습니다
Room path 는
/rooms/:roomCode로 수정했습니다 (관례적으로 rooms 를 많이 사용해서 복수형을 사용했습니다)