Skip to content
7 changes: 6 additions & 1 deletion apps/what-today/src/components/auth/AgreeCheckbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ function AgreeCheckbox({ error, content, label, required = false, ...props }: Ag
<>
<div className='flex w-full items-center'>
<label className='flex cursor-pointer items-center gap-8' htmlFor={id}>
<input id={id} type='checkbox' {...props} className='cursor-pointer' />
<input
id={id}
type='checkbox'
{...props}
className='cursor-pointer accent-black focus:ring-black/30 focus:outline-none'
/>
<p className='caption-text'>
{label} {required && <span className='text-primary-500'>(필수)</span>}
</p>
Expand Down
4 changes: 2 additions & 2 deletions apps/what-today/src/components/auth/AgreeContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const termsOfServiceContent = (
);

export const privacyPolicyContent = (
<div className='flex flex-col gap-12'>
<div className='body-text flex flex-col gap-12'>
<div>
<p className='font-bold'>1. 수집 항목</p>
<p>회사는 회원 가입 및 서비스 이용을 위해 아래와 같은 개인정보를 수집합니다.</p>
Expand All @@ -62,7 +62,7 @@ export const privacyPolicyContent = (
);

export const locationTermsContent = (
<div className='flex flex-col gap-12'>
<div className='body-text flex flex-col gap-12'>
<div>
<p className='font-bold'>제1조 목적</p>
<p>본 약관은 위치정보 기반 서비스 이용과 관련한 회원의 권리 및 의무를 규정합니다.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-q
import {
BellIcon,
Button,
DotIcon,
NotificationCard,
NotificationCardSkeleton,
Popover,
Expand Down Expand Up @@ -127,19 +126,19 @@ export default function NotificationPopover({ isMobile }: NotificationPopoverPro
variant='none'
onClick={() => setOpen((prev) => !prev)}
>
<DotIcon
{/* <DotIcon
aria-label='새 알림 있음'
className='absolute top-2 left-12 size-8'
color='var(--color-red-500)'
id='notification-dot'
/>
/> */}
<BellIcon className='size-20' color={open ? 'var(--color-primary-500)' : 'var(--color-gray-600)'} />
</Button>
</Popover.Trigger>
<Popover.Content className='mt-8 rounded-2xl border border-gray-100 bg-white p-10 shadow-[0_4px_24px_rgba(156,180,202,0.2)]'>
<h1 className='my-8 ml-auto px-16 font-bold text-gray-950'>알림 {data?.pages[0].totalCount ?? 0}개</h1>

<section ref={scrollContainerRef} className='relative max-h-400 w-300 overflow-y-scroll'>
<section ref={scrollContainerRef} className='relative max-h-400 w-300 overflow-y-auto'>
{isLoading && (
<>
<NotificationCardSkeleton />
Expand Down
58 changes: 29 additions & 29 deletions apps/what-today/src/pages/main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ export default function MainPage() {
return (
<>
<div className='to-primary-500/40 absolute top-0 left-0 h-1/2 w-full bg-gradient-to-t from-transparent' />

<div className='relative z-10 flex h-auto flex-col gap-60'>
<div className='relative z-10 flex h-auto flex-col gap-100'>
<MainBanner />

{/* 인기 체험 */}
Expand Down Expand Up @@ -196,20 +195,20 @@ export default function MainPage() {
<h2 className='title-text flex items-center gap-12'>🛼 모든 체험</h2>
{/* 모바일에서만 보이는 가격 드롭다운 */}
<Select.Root value={selectedValue} onChangeValue={handleSortChange}>
<Select.Trigger className='flex min-w-fit gap-6 rounded-lg border border-gray-300 bg-white px-8 text-sm'>
<Select.Value className='body-text text-gray-950' placeholder='가격' />
</Select.Trigger>
<Select.Content>
<Select.Group className='caption-text text-center whitespace-nowrap'>
<Select.Item className='flex justify-center' value='desc'>
높은순
</Select.Item>
<Select.Item className='flex justify-center' value='asc'>
낮은순
</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
<Select.Trigger className='flex min-w-fit gap-6 rounded-xl border border-gray-100 bg-white py-6'>
<Select.Value className='body-text text-gray-950' placeholder='가격' />
</Select.Trigger>
<Select.Content>
<Select.Group className='text-center whitespace-nowrap'>
<Select.Item className='flex justify-center' value='desc'>
높은순
</Select.Item>
<Select.Item className='flex justify-center' value='asc'>
낮은순
</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
</div>

{/* 데스크톱/태블릿에서만 보이는 제목 */}
Expand All @@ -222,35 +221,36 @@ export default function MainPage() {
selectedValue={selectedCategory}
onSelect={handleCategoryChange}
>
<RadioGroup.Radio className='flex gap-8' value='문화 · 예술'>
<RadioGroup.Radio className='flex gap-8 font-normal' value='문화 · 예술'>
<ArtIcon className='size-12' /> 문화 예술
</RadioGroup.Radio>
<RadioGroup.Radio value='식음료'>
<RadioGroup.Radio className='font-normal' value='식음료'>
<FoodIcon className='size-12' /> 식음료
</RadioGroup.Radio>
<RadioGroup.Radio value='스포츠'>
<RadioGroup.Radio className='font-normal' value='스포츠'>
<SportIcon className='size-12' /> 스포츠
</RadioGroup.Radio>
<RadioGroup.Radio value='투어'>
<RadioGroup.Radio className='font-normal' value='투어'>
<WellbeingIcon className='size-12' /> 투어
</RadioGroup.Radio>
<RadioGroup.Radio value='관광'>
<RadioGroup.Radio className='font-normal' value='관광'>
<BusIcon className='size-12' /> 관광
</RadioGroup.Radio>
<RadioGroup.Radio value='웰빙'>
<RadioGroup.Radio className='font-normal' value='웰빙'>
<TourIcon className='size-12' /> 웰빙
</RadioGroup.Radio>
</RadioGroup>

{/* 데스크톱/태블릿에서만 보이는 가격 드롭다운 */}
<div className='hidden md:block'>
<Select.Root value={selectedValue} onChangeValue={handleSortChange}>
<Select.Trigger className='flex min-w-fit gap-6 rounded-lg border border-gray-300 bg-white px-8 text-sm'>
<Select.Value className='body-text text-gray-950' placeholder='가격' />
</Select.Trigger>
<Select.Content>
<Select.Group className='caption-text text-center whitespace-nowrap'>
<Select.Item className='flex justify-center' value='desc'>
높은순
<Select.Trigger className='flex min-w-fit gap-6 rounded-xl border border-gray-100 bg-white py-6'>
<Select.Value className='body-text text-gray-950' placeholder='가격' />
</Select.Trigger>
<Select.Content>
<Select.Group className='text-center whitespace-nowrap'>
<Select.Item className='flex justify-center' value='desc'>
높은순
</Select.Item>
<Select.Item className='flex justify-center' value='asc'>
낮은순
Expand Down
5 changes: 3 additions & 2 deletions apps/what-today/src/pages/signup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,15 @@ export default function SignupPage() {
)}

<div className='flex w-full flex-col items-start justify-center gap-2'>
<label className='mb-4 flex cursor-pointer gap-8'>
<label className='mb-4 flex cursor-pointer items-center gap-8' htmlFor='all-agree'>
<input
checked={
(watch('agreeToTerms') ?? false) &&
(watch('agreeToPrivacy') ?? false) &&
(watch('agreeToLocation') ?? false)
}
className='cursor-pointer'
className='cursor-pointer accent-black focus:ring-black/30 focus:outline-none'
id='all-agree'
type='checkbox'
onChange={(e) => {
const checked = e.target.checked;
Expand Down
22 changes: 15 additions & 7 deletions apps/what-today/src/schemas/auth.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { z } from 'zod';

/**
* 공통 비밀번호 정책
* - 8자 이상
* - 숫자 1자 이상
* - 영문 대/소문자 1자 이상
* - 특수문자 1자 이상
*/
export const PASSWORD_REGEX = /^(?!.*\s)(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/;
const PASSWORD_POLICY_MSG = '비밀번호는 영문 대/소문자, 숫자, 특수문자 포함 8자 이상입니다.';

/**
* From server
* @description 사용자 정보 스키마
Expand Down Expand Up @@ -29,7 +39,8 @@ export const signUpFormSchema = z
password: z
.string()
.min(1, { message: '비밀번호를 입력해 주세요.' })
.min(8, { message: '비밀번호는 8자 이상이어야 합니다.' }),
// .min(8, { message: '비밀번호는 8자 이상이어야 합니다.' }),
.refine((val) => PASSWORD_REGEX.test(val), { message: PASSWORD_POLICY_MSG }),
passwordConfirm: z.string().min(1, { message: '비밀번호 확인을 입력해 주세요.' }),
agreeToTerms: z.boolean().refine((val) => val === true, {
message: '이용약관에 동의해야 회원가입이 가능합니다.',
Expand Down Expand Up @@ -129,14 +140,11 @@ export const updateMyProfileSchema = z

// password가 1글자 이상 입력된 경우만 검사
if (password && password.length > 0) {
if (password.length < 8) {
if (!PASSWORD_REGEX.test(password)) {
ctx.addIssue({
path: ['password'],
code: z.ZodIssueCode.too_small,
minimum: 8,
type: 'string',
inclusive: true,
message: '비밀번호는 8자 이상이어야 합니다.',
code: z.ZodIssueCode.custom,
message: PASSWORD_POLICY_MSG,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ export default function MainSearchInput({ onClick }: MainSearchInputProps) {
}, [value, onClick, hasSearched]);

return (
<div className='relative flex w-full items-center justify-between bg-white'>
<div className='relative mx-auto flex w-full max-w-700 items-center justify-between bg-white'>
<Input.Root className='w-full'>
<Input.Wrapper className='body-textn relative rounded-3xl border-gray-50 py-20'>
<Input.Wrapper className='body-text relative rounded-3xl border border-gray-100 py-20'>
<div className='absolute inset-y-0 left-0 flex items-center pl-20 md:pl-32'>
<SearchIcon className='cursor-pointer text-gray-400' />
<SearchIcon className='size-20 cursor-pointer' color='var(--color-primary-500)' />
</div>
<Input.Field
className='px-30 md:px-40'
className='px-35 md:px-45'
placeholder='내가 원하는 체험은...'
value={value}
onChange={(e) => setValue(e.target.value)}
Expand Down
8 changes: 4 additions & 4 deletions packages/design-system/src/components/MypageProfileHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ interface MypageProfileHeaderProps {
export default function MypageProfileHeader({ name, email, profileImageUrl, onLogoutClick }: MypageProfileHeaderProps) {
return (
<div className='flex items-center gap-36 rounded-3xl border border-gray-50 bg-white px-36 py-36'>
<div className='flex size-120 items-center justify-center rounded-full border border-gray-50 bg-white'>
<div className='flex aspect-square size-120 items-center justify-center rounded-full border border-gray-50 bg-white'>
{profileImageUrl ? (
<img
alt='프로필 이미지'
className='size-110 rounded-full border border-gray-50 bg-white object-cover'
className='aspect-square size-110 rounded-full border border-gray-50 bg-white object-cover'
src={profileImageUrl}
/>
) : (
Expand All @@ -26,8 +26,8 @@ export default function MypageProfileHeader({ name, email, profileImageUrl, onLo

<div className='flex h-80 flex-col justify-center gap-16 text-gray-950'>
<div className='flex flex-col'>
<p className='title-text font-bold'>{name}</p>
<p className='body-text text-gray-400'>{email}</p>
<p className='title-text line-clamp-1 font-bold'>{name}</p>
<p className='body-text line-clamp-1 text-gray-400'>{email}</p>
</div>
<Button
className='caption-text h-auto w-auto justify-start p-0 text-gray-400'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ RadioGroup.Radio = function Radio({ value, children, className = '', name = 'rad
};

const BASE_STYLE =
'flex gap-8 caption-text cursor-pointer items-center rounded-full border px-10 py-6 md:px-14 md:py-6 font-bold whitespace-nowrap transition-all duration-300 ease-in-out';
const SELECTED_STYLE = 'bg-black text-white ';
const UNSELECTED_STYLE = 'border-gray-300 bg-white text-gray-700 hover:bg-gray-100';
'flex gap-8 body-text cursor-pointer items-center rounded-full border px-10 py-6 md:px-14 md:py-6 font-bold whitespace-nowrap transition-all duration-300 ease-in-out';
const SELECTED_STYLE = 'bg-black text-white';
const UNSELECTED_STYLE = 'border-gray-100 bg-white hover:bg-gray-50';

const mergedClassName = twMerge(
BASE_STYLE,
Expand Down
4 changes: 2 additions & 2 deletions packages/design-system/src/components/TimePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default function TimePicker({ value, onChange, className, disabled = fals

<Select.Content className='flex gap-4 rounded-2xl border border-gray-100 bg-white p-10 shadow-sm'>
{/* 시 선택 */}
<div className='flex flex-1 flex-col gap-4 overflow-y-scroll pr-4'>
<div className='flex flex-1 flex-col gap-4 overflow-y-auto pr-4'>
{Array.from({ length: 24 }).map((_, i) => {
const hour = String(i).padStart(2, '0');
const isSelected = value?.hour === hour;
Expand All @@ -91,7 +91,7 @@ export default function TimePicker({ value, onChange, className, disabled = fals
</div>

{/* 분 선택 */}
<div className='flex flex-1 flex-col gap-4 overflow-y-scroll pr-4'>
<div className='flex flex-1 flex-col gap-4 overflow-y-auto pr-4'>
{Array.from({ length: 12 }).map((_, i) => {
const minute = String(i * 5).padStart(2, '0');
const isSelected = value?.minute === minute;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function SelectContent({ className, children }: BaseProps) {
<Popover.Content
matchTriggerWidth
className={twMerge(
'select-content mt-4 max-h-300 overflow-y-scroll rounded-2xl border border-gray-100 bg-white p-10 shadow-[0_4px_24px_rgba(156,180,202,0.2)]',
'select-content mt-4 max-h-300 overflow-y-auto rounded-2xl border border-gray-100 bg-white p-10 shadow-[0_4px_24px_rgba(156,180,202,0.2)]',
className,
)}
>
Expand Down