diff --git a/apps/admin/src/components/AuthoritySettingDialogContent/components/HostListItem/index.tsx b/apps/admin/src/components/AuthoritySettingDialogContent/components/HostListItem/index.tsx index 59ee42e8..00d278f9 100644 --- a/apps/admin/src/components/AuthoritySettingDialogContent/components/HostListItem/index.tsx +++ b/apps/admin/src/components/AuthoritySettingDialogContent/components/HostListItem/index.tsx @@ -1,7 +1,7 @@ import { useDropdown } from '@boolti/ui'; import Styled from './HostListItem.styles'; import { HostListItem as IHostListItem, HostType, HostTypeInfo } from '@boolti/api/src/types/host'; -import { CheckIcon, ChevronDownIcon } from '@boolti/icon'; +import { CheckIcon, ChevronDownIcon, UserIcon } from '@boolti/icon'; import { useAtom } from 'jotai'; import { myHostInfoAtom } from '~/components/ShowDetailLayout'; @@ -11,25 +11,6 @@ interface HostListItemProps { onEdit: (host: IHostListItem, type: HostType) => void; } -const ProfileSVG = () => ( - - - - - - - - - - -); - const dropdownItems: HostTypeInfo[] = [ { type: HostType.MANAGER, @@ -80,11 +61,7 @@ const HostListItem = ({ host, onEdit, onDelete }: HostListItemProps) => { return ( - {host.imagePath ? ( - - ) : ( - - )} + {host.imagePath ? : } {host.hostName} {host.self && (나)} diff --git a/apps/admin/src/components/Layout/Layout.styles.ts b/apps/admin/src/components/Layout/Layout.styles.ts index d08019b7..180090cb 100644 --- a/apps/admin/src/components/Layout/Layout.styles.ts +++ b/apps/admin/src/components/Layout/Layout.styles.ts @@ -25,12 +25,7 @@ const BannerContainer = styled.div` `; const Banner = styled.div` - max-width: ${({ theme }) => theme.breakpoint.desktop}; min-height: 56px; - margin: 0 auto; - display: flex; - align-items: center; - padding: 0 20px; `; const ContentContainer = styled.div` diff --git a/apps/admin/src/components/ProfileDropdown/index.tsx b/apps/admin/src/components/ProfileDropdown/index.tsx index 71aa0675..02e7a7fd 100644 --- a/apps/admin/src/components/ProfileDropdown/index.tsx +++ b/apps/admin/src/components/ProfileDropdown/index.tsx @@ -1,5 +1,5 @@ import { queryKeys, useLogout, useQueryClient } from '@boolti/api'; -import { ChevronDownIcon, ChevronUpIcon, LogoutIcon, SettingIcon } from '@boolti/icon'; +import { ChevronDownIcon, ChevronUpIcon, LogoutIcon, SettingIcon, UserIcon } from '@boolti/icon'; import { useConfirm, useDialog, useDropdown } from '@boolti/ui/src/hooks'; import { useNavigate } from 'react-router-dom'; @@ -16,26 +16,6 @@ interface ProfileDropdownProps { onClick?: () => void; } -// TODO: UserProfile svg 공통화 -const ProfileSVG = () => ( - - - - - - - - - - -); - const ProfileDropdown = ({ image, open, disabledDropdown, onClick }: ProfileDropdownProps) => { const { isOpen, toggleDropdown } = useDropdown(); const { removeToken } = useAuthAtom(); @@ -58,7 +38,7 @@ const ProfileDropdown = ({ image, open, disabledDropdown, onClick }: ProfileDrop }} > - {image ? : } + {image ? : } {dropdownOpen ? : } diff --git a/apps/admin/src/components/ShowCastInfo/index.tsx b/apps/admin/src/components/ShowCastInfo/index.tsx index dcd985d7..05d7ae62 100644 --- a/apps/admin/src/components/ShowCastInfo/index.tsx +++ b/apps/admin/src/components/ShowCastInfo/index.tsx @@ -1,7 +1,7 @@ import { useDialog } from '@boolti/ui'; import Styled from './ShowCastInfo.styles'; -import { EditIcon, ChevronDownIcon, ChevronUpIcon } from '@boolti/icon'; +import { EditIcon, ChevronDownIcon, ChevronUpIcon, UserIcon } from '@boolti/icon'; import { useState } from 'react'; import ShowCastInfoFormDialogContent, { TempShowCastInfoFormInput, @@ -71,9 +71,13 @@ const ShowCastInfo = ({ showCastInfo, onSave, onDelete }: Props) => { > {members.map((member) => ( - + {member.userImgPath ? ( + + ) : ( + + )} {member.userNickname} ({member.roleName}) diff --git a/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx b/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx index 7b725fea..ace1e584 100644 --- a/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx +++ b/apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx @@ -3,7 +3,7 @@ import { Controller, useFieldArray, useForm } from 'react-hook-form'; import Styled from './ShowCastInfoFormDialogContent.styles'; import { useState } from 'react'; import { useBodyScrollLock } from '~/hooks/useBodyScrollLock'; -import { ClearIcon, PlusIcon, TrashIcon } from '@boolti/icon'; +import { ClearIcon, PlusIcon, TrashIcon, UserIcon } from '@boolti/icon'; import { Member, queryKeys, useQueryClient } from '@boolti/api'; import { replaceUserCode } from '~/utils/replace'; @@ -61,9 +61,9 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P !getValues('name') || ((memberFieldState.isDirty || memberFieldState.isTouched) && controlledFields.some( - ({ userImgPath, userNickname, roleName }, index) => + ({ userNickname, roleName }, index) => (isMemberFieldBlurred[index].roleName || isMemberFieldBlurred[index].userCode) && - (!userImgPath || !userNickname || !roleName), + (!userNickname || !roleName), )); return ( @@ -102,22 +102,24 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P render={({ field: { onChange, onBlur } }) => { const value = field.userCode; const isError = Boolean( - isMemberFieldBlurred[index].userCode - ? !value || !field.userImgPath || !field.userNickname - : false, + isMemberFieldBlurred[index].userCode ? !value || !field.userNickname : false, ); return ( - {field.userImgPath && field.userNickname ? ( + {field.userNickname ? ( <> - + {field.userImgPath ? ( + + ) : ( + + )} {field.userNickname} { @@ -278,8 +280,7 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P const name = getValues('name'); const members = (getValues('members') ?? []).filter( - (member) => - member.userImgPath && member.userNickname && member.roleName && member.userCode, + (member) => member.userNickname && member.roleName && member.userCode, ); try { diff --git a/apps/admin/src/components/ShowInfoFormContent/ShowBasicInfoFormContent.tsx b/apps/admin/src/components/ShowInfoFormContent/ShowBasicInfoFormContent.tsx index fea5be56..1c03fa55 100644 --- a/apps/admin/src/components/ShowInfoFormContent/ShowBasicInfoFormContent.tsx +++ b/apps/admin/src/components/ShowInfoFormContent/ShowBasicInfoFormContent.tsx @@ -2,7 +2,7 @@ import { ImageFile } from '@boolti/api'; import { CloseIcon, FileUpIcon } from '@boolti/icon'; import { Button, TextField, TimePicker, useDialog } from '@boolti/ui'; import { add, format } from 'date-fns'; -import { useRef, useState } from 'react'; +import { useRef } from 'react'; import { useDropzone } from 'react-dropzone'; import { Controller, UseFormReturn } from 'react-hook-form'; import DaumPostcode from 'react-daum-postcode'; @@ -12,8 +12,7 @@ import { ShowInfoFormInputs } from './types'; import { useBodyScrollLock } from '~/hooks/useBodyScrollLock'; const MAX_IMAGE_COUNT = 3; - -type ShowBasicInfoFormInputs = Omit; +const MIN_DATE = format(add(new Date(), { days: 1 }), 'yyyy-MM-dd') interface ShowBasicInfoFormContentProps { form: UseFormReturn; @@ -31,28 +30,18 @@ const ShowBasicInfoFormContent = ({ onDeleteImage, }: ShowBasicInfoFormContentProps) => { const { open, close, isOpen } = useDialog(); - const detailAdressInputRef = useRef(null); + const detailAddressInputRef = useRef(null); - const { control, setValue } = form; + const { control, setValue, formState: { errors }, setError, clearErrors } = form; const { getRootProps, getInputProps } = useDropzone({ accept: { - 'image/*': [], + 'image/jpeg, image/png': [], }, maxFiles: MAX_IMAGE_COUNT, onDrop: onDropImage, }); - const [hasBlurred, setHasBlurred] = useState>({ - name: false, - date: false, - startTime: false, - runningTime: false, - placeName: false, - placeStreetAddress: false, - placeDetailAddress: false, - }); - const openDaumPostCodeWithDialog: React.MouseEventHandler = (e) => { e.preventDefault(); open({ @@ -62,10 +51,9 @@ const ShowBasicInfoFormContent = ({ style={{ maxWidth: 426, height: 470 }} onComplete={(address) => { setValue('placeStreetAddress', address.roadAddress); - detailAdressInputRef.current?.focus(); + detailAddressInputRef.current?.focus(); }} onClose={() => { - setHasBlurred((prev) => ({ ...prev, placeStreetAddress: true })); close(); }} /> @@ -87,8 +75,9 @@ const ShowBasicInfoFormContent = ({ 공연 포스터 - 원하시는 노출 순서대로 이미지를 업로드해주세요. (최소 1장, 최대{' '} - {MAX_IMAGE_COUNT}장 업로드 가능 / jpg, png 형식) + 원하시는 노출 순서대로 이미지를 업로드해주세요.  + 표준 종이규격(A, B)의 이미지를 권장합니다.
+ (최소 1장, 최대 {MAX_IMAGE_COUNT}장 업로드 가능 / jpg, png 형식)
{imageFiles.map((file, index) => ( @@ -143,13 +132,20 @@ const ShowBasicInfoFormContent = ({ placeholder="공연명을 입력해 주세요 (띄어쓰기 포함 최대 40자)" required disabled={disabled} - onChange={onChange} + onChange={(event) => { + onChange(event); + clearErrors('name'); + }} onBlur={() => { onBlur(); - setHasBlurred((prev) => ({ ...prev, name: true })); + + if (!value) { + setError('name', { type: 'required', message: '필수 입력사항입니다.' }); + return + } }} value={value ?? ''} - errorMessage={hasBlurred.name && !value ? '필수 입력사항입니다.' : undefined} + errorMessage={errors.name?.message} /> )} name="name" @@ -164,23 +160,36 @@ const ShowBasicInfoFormContent = ({ ( { + onChange(event); + clearErrors('date'); + + if (new Date(event.target.value) < new Date(MIN_DATE)) { + setError('date', { type: 'min', message: '오늘 이후부터 선택 가능합니다.' }); + return + } + }} onBlur={() => { onBlur(); - setHasBlurred((prev) => ({ ...prev, date: true })); + + if (!value) { + setError('date', { type: 'required', message: '필수 입력사항입니다.' }); + return + } }} placeholder={value} - min={format(add(new Date(), { days: 1 }), 'yyyy-MM-dd')} + min={MIN_DATE} required disabled={disabled} - value={value ?? ''} - errorMessage={hasBlurred.date && !value ? '필수 입력사항입니다.' : undefined} + value={value} + errorMessage={errors.date?.message} /> )} name="date" @@ -190,7 +199,7 @@ const ShowBasicInfoFormContent = ({ - 공연 시작 시간 + 시작 시간 { + onChange(event); + clearErrors('startTime'); + }} onBlur={() => { onBlur(); - setHasBlurred((prev) => ({ ...prev, startTime: true })); + + if (!value) { + setError('startTime', { type: 'required', message: '필수 입력사항입니다.' }); + return + } }} value={value} - errorMessage={ - hasBlurred.startTime && !value ? '필수 입력사항입니다.' : undefined - } + errorMessage={errors.startTime?.message} /> ); }} @@ -232,15 +246,20 @@ const ShowBasicInfoFormContent = ({ min={0} required disabled={disabled} - onChange={onChange} + onChange={(event) => { + onChange(event); + clearErrors('runningTime'); + }} onBlur={() => { onBlur(); - setHasBlurred((prev) => ({ ...prev, runningTime: true })); + + if (!value) { + setError('runningTime', { type: 'required', message: '필수 입력사항입니다.' }); + return + } }} value={value ?? ''} - errorMessage={ - hasBlurred.runningTime && !value ? '필수 입력사항입니다.' : undefined - } + errorMessage={errors.runningTime?.message} /> )} name="runningTime" @@ -265,13 +284,20 @@ const ShowBasicInfoFormContent = ({ placeholder="공연장명을 입력해 주세요" required disabled={disabled} - onChange={onChange} + onChange={(event) => { + onChange(event); + clearErrors('placeName'); + }} onBlur={() => { onBlur(); - setHasBlurred((prev) => ({ ...prev, placeName: true })); + + if (!value) { + setError('placeName', { type: 'required', message: '필수 입력사항입니다.' }); + return + } }} value={value ?? ''} - errorMessage={hasBlurred.placeName && !value ? '필수 입력사항입니다.' : undefined} + errorMessage={errors.placeName?.message} /> )} name="placeName" @@ -297,9 +323,7 @@ const ShowBasicInfoFormContent = ({ required disabled value={value ?? ''} - errorMessage={ - hasBlurred.placeStreetAddress && !value ? '필수 입력사항입니다.' : undefined - } + errorMessage={errors.placeStreetAddress?.message} />