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
1 change: 1 addition & 0 deletions src/assets/icons/landing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export { default as YogaImage } from './yoga.png';
export { default as BicycleImage } from './bicycle.png';
export { default as HeroSectionCardImage } from './hero_section_card.svg';
export { default as MobileHeroSectionCardImage } from './mobile_hero_section_card.svg';
export { default as LandingInfoIcon } from './landing_info.svg';
3 changes: 3 additions & 0 deletions src/assets/icons/landing/landing_info.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/landing/CTASection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function CTASection({ className }: { className?: string }) {
<div className="flex gap-3">
<InquiryDialog>
<button className="typo-button1 flex-1 rounded-md bg-[#00C8AA] px-400 py-300 text-white hover:bg-[#00877a]">
사전 예약하기
가입 문의하기
</button>
</InquiryDialog>
</div>
Expand Down
32 changes: 18 additions & 14 deletions src/components/landing/InquiryDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,35 @@ import {
Input,
Textarea,
} from '@/components/ui';
import { TimeIcon } from '@/assets/icons';
import { LandingInfoIcon } from '@/assets/icons/landing';
import { inquiryApi } from '@/lib/apis/inquiry';
import { toastSuccess, toastError } from '@/stores/useToastStore';
import Image from 'next/image';

interface InquiryDialogProps {
children: ReactNode;
children?: ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}

function InquiryDialog({ children }: InquiryDialogProps) {
const [open, setOpen] = useState(false);
function InquiryDialog({ children, open, onOpenChange }: InquiryDialogProps) {
const [internalOpen, setInternalOpen] = useState(false);
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const dialogOpen = open ?? internalOpen;
const setDialogOpen = onOpenChange ?? setInternalOpen;

const handleClose = () => {
if (isSubmitting) return;
setOpen(false);
setDialogOpen(false);
setEmail('');
setMessage('');
};

const handleOpenChange = (next: boolean) => {
if (!next) handleClose();
else setOpen(true);
else setDialogOpen(true);
};

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
Expand All @@ -56,22 +60,22 @@ function InquiryDialog({ children }: InquiryDialogProps) {
};

return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>{children}</DialogTrigger>
<Dialog open={dialogOpen} onOpenChange={handleOpenChange}>
{children && <DialogTrigger asChild>{children}</DialogTrigger>}
<DialogContent
showCloseButton={false}
className="flex w-[640px] flex-col bg-[#F3F5F7]"
onPointerDownOutside={isSubmitting ? (e) => e.preventDefault() : undefined}
onInteractOutside={isSubmitting ? (e) => e.preventDefault() : undefined}
>
<DialogHeader
icon={<Image src={TimeIcon} width={24} height={24} alt="정보 아이콘" />}
title={<span className="typo-h2 text-black">사전예약</span>}
icon={<Image src={LandingInfoIcon} width={24} height={24} alt="정보 아이콘" />}
title={<span className="typo-h2 text-black">가입 문의</span>}
description={
<span className="typo-body2 text-[#909599]">
Weeth가 출시되면 메일로 가장 먼저 알려드릴게요!
Weeth 도입이 궁금하신가요?
<br />
추가 문의사항을 작성해 주시면 빠른 시일 내로 답변드리겠습니다.
가입 문의를 남겨주시면 안내해드릴게요.
</span>
}
showClose
Expand All @@ -83,7 +87,7 @@ function InquiryDialog({ children }: InquiryDialogProps) {
<div className="flex flex-col gap-400">
<div className="flex flex-col gap-200">
<label htmlFor="inquiry-email" className="typo-caption1 text-[#909599]">
알림 받을 메일
연락 가능한 이메일
</label>
<Input
id="inquiry-email"
Expand Down Expand Up @@ -129,7 +133,7 @@ function InquiryDialog({ children }: InquiryDialogProps) {
className="typo-button1 flex-1 rounded-md bg-[#00C8AA] px-400 py-300 text-white hover:bg-[#00877a]"
disabled={isSubmitting}
>
사전예약 완료
가입 문의
</button>
</div>
</DialogFooter>
Expand Down
2 changes: 1 addition & 1 deletion src/components/landing/heroSection.shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function HeroSectionCTA() {
<div className="flex gap-3">
<InquiryDialog>
<Button className="block w-fit rounded-md bg-[#00C8AA] px-400 py-300 text-[16px] leading-[24px] font-semibold tracking-[-0.005em] text-white hover:bg-[#00877a]">
사전 예약하기
가입 문의하기
</Button>
</InquiryDialog>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default function Header({ isMain = true }: HeaderProps) {
href="#"
className="typo-button1 text-text-alternative hover:text-text-normal transition-colors"
>
사전예약
가입문의
</Link>
</>
)}
Expand Down
198 changes: 153 additions & 45 deletions src/components/layout/header/PublicHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,106 @@ import { useEffect, useRef, useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { LogoIcon } from '@/assets/icons';
import { buttonVariants } from '@/components/ui';
import { DeleteIcon, LogoIcon, MenuIcon } from '@/assets/icons';
import {
buttonVariants,
Icon,
Sheet,
SheetClose,
SheetContent,
SheetTrigger,
} from '@/components/ui';
import { cn } from '@/lib/cn';
import { NAV_ITEMS } from '@/constants/landing/landing';
import { InquiryDialog } from '@/components/landing/InquiryDialog';

interface PublicHeaderProps {
className?: string;
showAuthButtons?: boolean;
}

function PublicMobileMenu({ showAuthButtons }: { showAuthButtons: boolean }) {
const [inquiryOpen, setInquiryOpen] = useState(false);

return (
<>
<InquiryDialog open={inquiryOpen} onOpenChange={setInquiryOpen} />
<Sheet>
<SheetTrigger asChild>
<button
type="button"
aria-label="메뉴 열기"
className="flex cursor-pointer items-center justify-center rounded-sm outline-none"
>
<Icon src={MenuIcon} alt="menu" size={40} className="text-icon-normal p-2" />
</button>
</SheetTrigger>
<SheetContent
side="right"
overlayClassName="bg-transparent"
className="data-[state=closed]:slide-out-to-right-0 data-[state=open]:slide-in-from-right-0 top-0 h-dvh w-full max-w-none bg-white shadow-none duration-0 data-[state=closed]:animate-none data-[state=open]:animate-none"
>
<div className="flex items-center justify-between px-450 py-3">
<SheetClose asChild>
<Link href="/landing" aria-label="홈으로 이동">
<Image
src={LogoIcon}
alt="Weeth-logo"
width={90}
height={40}
className="h-[40px] w-[90px]"
/>
</Link>
</SheetClose>
<SheetClose asChild>
<button
type="button"
aria-label="메뉴 닫기"
className="flex cursor-pointer items-center justify-center rounded-sm outline-none"
>
<Icon src={DeleteIcon} alt="close" size={40} className="text-icon-normal p-2" />
</button>
</SheetClose>
</div>

<nav className="flex flex-col items-start gap-200 px-450 py-400" aria-label="랜딩 메뉴">
<SheetClose asChild>
<button
type="button"
className="cursor-pointer py-300 text-[24px] leading-[30px] font-bold tracking-[-0.005em]"
onClick={() => setInquiryOpen(true)}
>
가입문의
</button>
</SheetClose>

{showAuthButtons && (
<>
<SheetClose asChild>
<Link
href="/login"
className="cursor-pointer py-300 text-[24px] leading-[30px] font-bold tracking-[-0.005em]"
>
로그인
</Link>
</SheetClose>
<SheetClose asChild>
<Link
href="/login?intent=create"
className="cursor-pointer py-300 text-[24px] leading-[30px] font-bold tracking-[-0.005em]"
>
지금 무료로 시작하기
</Link>
</SheetClose>
</>
)}
</nav>
</SheetContent>
</Sheet>
</>
);
}

export default function PublicHeader({ className, showAuthButtons = true }: PublicHeaderProps) {
const [visible, setVisible] = useState(true);
const lastScrollY = useRef(0);
Expand Down Expand Up @@ -41,12 +131,12 @@ export default function PublicHeader({ className, showAuthButtons = true }: Publ
animate={{ y: visible ? 0 : -80, opacity: visible ? 1 : 0 }}
transition={{ duration: 0.25, ease: 'easeInOut' }}
className={cn(
'fixed top-0 left-0 z-1 flex w-full items-center justify-between bg-[#F3F5F7] px-450 py-300',
'fixed top-0 left-0 z-1 w-full bg-[#F3F5F7]',
!visible && 'pointer-events-none',
className,
)}
>
<div className="flex items-center gap-300">
<div className="tablet:hidden flex items-center justify-between bg-[#F3F5F7] px-450 py-3">
<Link href="/landing" aria-label="홈으로 이동">
<Image
src={LogoIcon}
Expand All @@ -59,51 +149,69 @@ export default function PublicHeader({ className, showAuthButtons = true }: Publ
}}
/>
</Link>
<nav className="flex items-center gap-300">
{NAV_ITEMS.map(({ id, label, href }) =>
id === 'contact' ? (
<InquiryDialog key={id}>
<button
type="button"
className="typo-button1 cursor-pointer whitespace-nowrap text-[#909599] transition-colors hover:text-black"
>
{label}
</button>
</InquiryDialog>
) : (
<Link
key={id}
href={href}
className="typo-button1 whitespace-nowrap text-[#909599] transition-colors hover:text-black"
>
{label}
</Link>
),
)}
</nav>
<PublicMobileMenu showAuthButtons={showAuthButtons} />
</div>
{showAuthButtons && (
<div className="flex items-center gap-200">
<Link
href="/login"
className={cn(
buttonVariants({ variant: 'secondary', size: 'md' }),
'bg-[#E6EAED] whitespace-nowrap text-black',
)}
>
로그인

<div className="tablet:flex hidden w-full items-center justify-between px-450 py-300">
<div className="flex items-center gap-300">
<Link href="/landing" aria-label="홈으로 이동">
<Image
src={LogoIcon}
alt="Weeth-logo"
width={90}
height={40}
className="h-[40px] w-[90px]"
onClick={() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}}
/>
</Link>
<Link
href="/login?intent=create"
className={cn(
buttonVariants({ variant: 'primary', size: 'md' }),
'bg-[#00C8AA] whitespace-nowrap text-white',
<nav className="flex items-center gap-300">
{NAV_ITEMS.map(({ id, label, href }) =>
id === 'contact' ? (
<InquiryDialog key={id}>
<button
type="button"
className="typo-button1 cursor-pointer whitespace-nowrap text-[#909599] transition-colors hover:text-black"
>
{label}
</button>
</InquiryDialog>
) : (
<Link
key={id}
href={href}
className="typo-button1 whitespace-nowrap text-[#909599] transition-colors hover:text-black"
>
{label}
</Link>
),
)}
>
지금 무료로 시작하기
</Link>
</nav>
</div>
)}
{showAuthButtons && (
<div className="flex items-center gap-200">
<Link
href="/login"
className={cn(
buttonVariants({ variant: 'secondary', size: 'md' }),
'bg-[#E6EAED] whitespace-nowrap text-black',
)}
>
로그인
</Link>
<Link
href="/login?intent=create"
className={cn(
buttonVariants({ variant: 'primary', size: 'md' }),
'bg-[#00C8AA] whitespace-nowrap text-white',
)}
>
지금 무료로 시작하기
</Link>
</div>
)}
</div>
</motion.header>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/constants/landing/landing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,5 @@ export const FOOTER_MENUS = [

export const NAV_ITEMS = [
// { id: 'service', label: '서비스 소개', href: '#service' },
{ id: 'contact', label: '사전예약', href: '#contact' },
{ id: 'contact', label: '가입문의', href: '#contact' },
];
Loading