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
29 changes: 12 additions & 17 deletions src/app/(landing)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,24 +88,19 @@ export default async function Landing() {
<>
<div>
{/* 메인 소개 */}
<section className="relative flex flex-col items-center gap-600 py-400 md:py-[80px]">
<section className="relative flex flex-col items-center gap-600 py-400 md:py-1000">
{/* 중앙에서 위아래로 퍼지는 그라데이션 배경 오버레이 */}
<div className="pointer-events-none absolute top-0 left-0 z-0 h-full w-full bg-gradient-to-b from-background-default via-rose-50 to-background-default" />

<div className="relative z-10 flex flex-col items-center justify-center gap-400">
<div className="relative z-10 flex flex-col items-center justify-center gap-400 px-400">
<div>
<p className="font-bold-h1 text-text-strong flex flex-col items-center">
<span>IT 업계 선배들이 진행하는</span>
<span>프리미엄 실전 스터디/외주 플랫폼</span>
<p className="font-bold-h4 sm:font-bold-h2 lg:font-bold-h1 text-text-strong break-keep text-center">
IT 업계 선배들이 진행하는 프리미엄 실전 스터디/외주 플랫폼
</p>

<p className="text-text-subtle font-designer-16m mt-200 flex flex-col items-center">
<span>
제로원은 수준 높은 IT 인재들의 신뢰와 네트워킹을 바탕으로
</span>
<span>
학습, 외주, 채용 등 전 생애 커리어를 구축하는 공간입니다.
</span>
<p className="text-text-subtle font-designer-14m sm:font-designer-16m mt-200 break-keep text-center">
제로원은 수준 높은 IT 인재들의 신뢰와 네트워킹을 바탕으로 학습,
외주, 채용 등 전 생애 커리어를 구축하는 공간입니다.
</p>
</div>
<Link href="/login">
Expand All @@ -130,11 +125,11 @@ export default async function Landing() {
sizes="(max-width: 1000px) 100vw, 1000px"
/>
{/* 배너 사진 하단 그라데이션 */}
<div className="pointer-events-none absolute bottom-0 left-0 h-[120px] w-full bg-gradient-to-t from-background-default to-transparent" />
<div className="pointer-events-none absolute bottom-0 left-0 h-1500 w-full bg-gradient-to-t from-background-default to-transparent" />
</div>
</section>
{/* 제로원 솔루션 소개 */}
<section className="mx-auto flex w-full max-w-[1160px] flex-col items-center gap-500 px-300 py-600 lg:px-0 md:py-[120px]">
<section className="mx-auto flex w-full max-w-study-content flex-col items-center gap-500 px-300 py-600 lg:px-0 md:py-1500">
<Badge className="rounded-200 w-fit">제로원 솔루션</Badge>

<p className="flex flex-col items-center gap-100">
Expand Down Expand Up @@ -179,7 +174,7 @@ export default async function Landing() {
{SUGGESTION_INFO_LIST.map((suggestion) => (
<section
key={suggestion.badge}
className="mx-auto flex w-full max-w-[1160px] flex-col items-center gap-400 px-300 py-600 lg:px-0 md:py-[120px] lg:flex-row lg:justify-between"
className="mx-auto flex w-full max-w-study-content flex-col items-center gap-400 px-300 py-600 lg:px-0 md:py-1500 lg:flex-row lg:justify-between"
>
{/* 왼쪽 컨텐츠 */}
<div className="flex-1">
Expand Down Expand Up @@ -214,7 +209,7 @@ export default async function Landing() {
{/* 전문가 멘토진 */}
{/* 다양한 스터디 */}
{/* 오픈 알림 폼 */}
<section className="mx-auto flex w-full max-w-[1160px] flex-col items-center gap-400 px-300 py-600 lg:px-0 md:py-[120px] lg:flex-row lg:justify-between">
<section className="mx-auto flex w-full max-w-study-content flex-col items-center gap-400 px-300 py-600 lg:px-0 md:py-1500 lg:flex-row lg:justify-between">
{/* 왼쪽 컨텐츠 */}
<div className="flex-1">
<Badge className="rounded-200 w-fit">
Expand Down Expand Up @@ -246,7 +241,7 @@ export default async function Landing() {
</section>
</div>

<footer className="bg-background-neutral-strong flex flex-col items-center gap-500 px-400 py-[64px] lg:px-800">
<footer className="bg-background-neutral-strong flex flex-col items-center gap-500 px-400 py-800 lg:px-800">
<div className="text-text-inverse font-designer-18b sm:font-bold-h3 flex flex-col items-center justify-center gap-200">
<span>제로원을 방문해주신 모든 분들에게 감사드립니다.</span>
<span>더욱 더 좋은 서비스와 기회로 보답하도록 하겠습니다!</span>
Expand Down
7 changes: 7 additions & 0 deletions src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,9 @@ https://velog.io/@oneook/tailwindcss-4.0-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%AC%E
--spacing-500: 40px;
--spacing-600: 48px;
--spacing-700: 56px;
--spacing-800: 64px;
--spacing-1000: 80px;
--spacing-1500: 120px;

/* shadow */
--shadow-*: initial;
Expand Down Expand Up @@ -486,6 +489,10 @@ https://velog.io/@oneook/tailwindcss-4.0-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%AC%E
max-width: 500px;
}

@utility max-w-1496 {
max-width: 1496px;
}

@utility max-h-modal {
max-height: 90vh;
}
Expand Down
13 changes: 7 additions & 6 deletions src/components/common/layout/home-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ export default async function Header() {
: undefined;

return (
<header className="bg-white py-[11px] mix-blend-multiply">
<div className="mx-auto flex w-full max-w-[1496px] items-center justify-between px-600">
<div className="flex items-center gap-[7.5px] px-100 py-[11px]">
<header className="bg-background-default py-125 mix-blend-multiply">
<div className="mx-auto flex w-full max-w-1496 items-center justify-between px-600">
<div className="flex items-center gap-100 px-100 py-125">
<Image src="/icons/logo.svg" alt="Logo" width={18} height={18} />
<Link href="/">
<Image
Expand All @@ -54,7 +54,7 @@ export default async function Header() {
height={11}
/>
</Link>
<span className="rounded-full border-[0.5px] border-[#D5D7DA] px-[5px] py-[2.5px] text-center text-[7.5px] leading-normal font-medium">
<span className="rounded-full border-[0.5px] border-border-default px-75 py-25 text-center text-[7.5px] leading-normal font-medium">
BETA
</span>
</div>
Expand Down Expand Up @@ -86,8 +86,9 @@ export default async function Header() {
</div>

{/* 모바일 햄버거 메뉴 */}
<div className="lg:hidden">
<MobileMenuDrawer isLoggedIn={isLoggedIn} />
<div className="flex items-center gap-100 lg:hidden">
{isLoggedIn && <NotificationDropdown />}
<MobileMenuDrawer isLoggedIn={isLoggedIn} userImg={userImg} />
</div>
</div>
</header>
Expand Down
52 changes: 37 additions & 15 deletions src/components/common/layout/mobile-menu-drawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';

import { cn } from '@/components/common/ui/(shadcn)/lib/utils';
import Avatar from '@/components/common/ui/avatar';
import StudyMatchingToggle from '@/components/home/study-matching-toggle';
import { useLogoutMutation } from '@/hooks/queries/use-auth-mutation';

const LoginModal = dynamic(
Expand All @@ -24,10 +26,12 @@ const NAV_ITEMS = [

interface MobileMenuDrawerProps {
isLoggedIn: boolean;
userImg?: string;
}

export default function MobileMenuDrawer({
isLoggedIn,
userImg,
}: MobileMenuDrawerProps) {
const [isOpen, setIsOpen] = useState(false);
const [mounted, setMounted] = useState(false);
Expand All @@ -46,7 +50,7 @@ export default function MobileMenuDrawer({
<button
onClick={() => setIsOpen(true)}
aria-label="메뉴 열기"
className="flex flex-col items-center justify-center gap-[5px] p-100"
className="flex flex-col items-center justify-center gap-75 p-100"
>
<svg
width="22"
Expand Down Expand Up @@ -102,7 +106,7 @@ export default function MobileMenuDrawer({
>
{/* 패널 헤더 */}
<div className="flex items-center justify-between border-b border-border-subtle px-400 py-300">
<div className="flex items-center gap-[7.5px]">
<div className="flex items-center gap-100">
<Image
src="/icons/logo.svg"
alt="Logo"
Expand All @@ -116,18 +120,29 @@ export default function MobileMenuDrawer({
height={11}
/>
</div>
<button
onClick={close}
aria-label="메뉴 닫기"
className="p-100"
>
<Image
src="/icons/modal-close.svg"
alt="닫기"
width={24}
height={24}
/>
</button>
<div className="flex items-center gap-200">
{isLoggedIn && (
<Link
href="/my-page"
onClick={close}
aria-label="마이페이지"
>
<Avatar image={userImg} size={32} />
</Link>
)}
<button
onClick={close}
aria-label="메뉴 닫기"
className="p-100"
>
<Image
src="/icons/modal-close.svg"
alt="닫기"
width={24}
height={24}
/>
</button>
</div>
</div>

{/* 네비게이션 */}
Expand All @@ -151,8 +166,15 @@ export default function MobileMenuDrawer({
})}
</nav>

{/* 1:1 스터디 매칭 토글 */}
{isLoggedIn && (
<div className="border-t border-border-subtle px-400 py-200">
<StudyMatchingToggle showLabel />
</div>
)}

{/* 하단 인증 버튼 */}
<div className="mt-auto border-t border-border-subtle px-400 py-300">
<div className="border-t border-border-subtle px-400 py-300">
{isLoggedIn ? (
<div className="flex flex-col gap-200">
<Link
Expand Down
28 changes: 18 additions & 10 deletions src/components/common/modals/notification-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DotIcon } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { MemberNotificationResponse } from '@/api/openapi';
import { cn } from '@/components/common/ui/(shadcn)/lib/utils';
import {
DropdownMenu,
DropdownMenuContent,
Expand Down Expand Up @@ -38,6 +39,7 @@ export default function NotificationDropdown() {
notification: MemberNotificationResponse,
) => {
if (!notification.isRead) {
if (!notification.id) return;
readNotifications([notification.id]);
}
};
Expand All @@ -61,25 +63,31 @@ export default function NotificationDropdown() {
)}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="shadow-2 rounded-100 border-border-default bg-background-default w-[520px] border">
<DropdownMenuContent
align="end"
collisionPadding={16}
className="shadow-2 rounded-100 border-border-default bg-background-default w-[min(520px,calc(100vw-32px))] border"
>
{/* Header */}
<div className="border-border-default flex items-center gap-150 border-b p-200">
<button
className={
className={cn(
'cursor-pointer',
mode === 'all'
? 'font-designer-12b text-text-subtle cursor-pointer'
: 'font-designer-12m text-text-subtlest cursor-pointer'
}
? 'font-designer-12b text-text-subtle'
: 'font-designer-12m text-text-subtlest',
)}
onClick={() => setMode('all')}
>
전체 {totalAllCount}
</button>
<button
className={
className={cn(
'cursor-pointer',
mode === 'unread'
? 'font-designer-12b text-text-subtle cursor-pointer'
: 'font-designer-12m text-text-subtlest cursor-pointer'
}
? 'font-designer-12b text-text-subtle'
: 'font-designer-12m text-text-subtlest',
)}
onClick={() => setMode('unread')}
>
안읽음 {totalUnreadCount}
Expand All @@ -103,7 +111,7 @@ export default function NotificationDropdown() {
</div>

{/* Footer */}
<div className="px-200 pb-[26px]">
<div className="px-200 pb-300">
<Button
color="outlined"
size="medium"
Expand Down
27 changes: 16 additions & 11 deletions src/components/forms/group-study-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import {
import { cn } from '@/components/common/ui/(shadcn)/lib/utils';
import Button from '@/components/common/ui/button';
import { Modal } from '@/components/common/ui/modal';
import Step1OpenGroupStudy from '@/components/forms/group-study-steps/step1-group';
import Step2OpenGroupStudy from '@/components/forms/group-study-steps/step2-group';
import Step3OpenGroupStudy from '@/components/forms/group-study-steps/step3-group';

import GroupStudyStepBasicInfo from '@/components/forms/group-study-steps/group-study-step-basic-info';
import GroupStudyStepIntroduction from '@/components/forms/group-study-steps/group-study-step-introduction';
import { useToastStore } from '@/stores/use-toast-store';
import {
GroupStudyFormSchema,
type GroupStudyFormValues,
type StudyClassification,
} from '@/types/schemas/group-study-form.schema';
import GroupStudyStepApplication from './group-study-steps/group-study-step-application';

const ClassificationContext = createContext<StudyClassification>('GROUP_STUDY');
export const useClassification = () => useContext(ClassificationContext);
Expand Down Expand Up @@ -55,6 +57,7 @@ export default function GroupStudyForm({
onSubmit,
mode = 'create',
}: GroupStudyFormProps) {
const showToast = useToastStore((state) => state.showToast);
const internalMethods = useForm<GroupStudyFormValues>({
resolver: zodResolver(GroupStudyFormSchema),
mode: 'onChange',
Expand All @@ -77,7 +80,7 @@ export default function GroupStudyForm({
shouldFocus: false,
});
if (!ok) {
console.log('trigger failed. errors:', methods.formState.errors);
showToast('스터디 개설 정보를 확인해주세요.', 'error');

return;
}
Expand Down Expand Up @@ -149,9 +152,9 @@ export default function GroupStudyForm({
className="flex flex-col gap-400"
onSubmit={handleSubmit(onSubmit)}
>
{step === 1 && <Step1OpenGroupStudy />}
{step === 2 && <Step2OpenGroupStudy />}
{step === 3 && <Step3OpenGroupStudy />}
{step === 1 && <GroupStudyStepBasicInfo />}
{step === 2 && <GroupStudyStepIntroduction />}
{step === 3 && <GroupStudyStepApplication />}
</form>
</FormProvider>
</Modal.Body>
Expand Down Expand Up @@ -223,7 +226,7 @@ function Stepper({ step }: { step: 1 | 2 | 3 }) {
<div
aria-current={isActive ? 'step' : undefined}
className={cn(
'font-designer-13b flex h-300 w-300 items-center justify-center rounded-full',
'font-designer-13b flex h-300 w-300 shrink-0 items-center justify-center rounded-full',
isActive && 'bg-background-brand-default text-text-inverse',
isCompleted && 'bg-background-brand-subtle text-text-brand',
!isActive &&
Expand All @@ -235,21 +238,23 @@ function Stepper({ step }: { step: 1 | 2 | 3 }) {
</div>
<span
className={cn(
'font-designer-14m',
'font-designer-13m whitespace-nowrap',
isActive && 'text-text-default',
isCompleted && 'text-text-subtle',
!isActive && !isCompleted && 'text-text-disabled',
)}
>
{STEP_LABELS[n]}
</span>
{n < 3 && <div className="bg-border-default mx-75 h-px w-300" />}
{n < 3 && (
<div className="bg-border-default mx-75 h-px flex-1 min-w-50" />
)}
</div>
);
};

return (
<nav aria-label="스터디 개설 단계" className="flex items-center">
<nav aria-label="스터디 개설 단계" className="flex w-full items-center">
{([1, 2, 3] as const).map((n) => renderStep(n))}
</nav>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import FormField from '@/components/common/ui/form/form-field';
import { BaseInput } from '@/components/common/ui/input';
import { GroupStudyFormValues } from '@/types/schemas/group-study-form.schema';

export default function Step3OpenGroupStudy() {
export default function GroupStudyStepApplication() {
const { setValue, getValues, formState } =
useFormContext<GroupStudyFormValues>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const memberOptions = Array.from({ length: 20 }, (_, i) => {
return { label: `${value}명`, value };
});

export default function Step1OpenGroupStudy() {
export default function GroupStudyStepBasicInfo() {
const { control, formState, watch } = useFormContext<GroupStudyFormValues>();
const classification = useClassification();
const mode = useMode();
Expand Down
Loading
Loading