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}
/>
-
-
- 대표자 이름
+
+
+ 대표자명
{
+ if (!fieldValue) return '필수 입력사항입니다.'
+
+ return true
+ }
}}
render={({ field: { onChange, onBlur, value } }) => (
{
+ onChange(event);
+ clearErrors('hostName')
+ }}
onBlur={() => {
onBlur();
- setHasBlurred((prev) => ({ ...prev, hostName: true }));
+ if (!value) {
+ setError('hostName', { type: 'required', message: '필수 입력사항입니다.' });
+ }
}}
value={value ?? ''}
- errorMessage={hasBlurred.hostName && !value ? '필수 입력사항입니다.' : undefined}
+ errorMessage={errors.hostName?.message}
/>
)}
name="hostName"
/>
-
-
대표자 연락처
@@ -100,6 +107,7 @@ const ShowDetailInfoFormContent = ({ form, disabled }: ShowDetailInfoFormContent
control={control}
rules={{
required: true,
+ pattern: phoneNumberRegExp,
}}
render={({ field: { onChange, onBlur, value } }) => (
{
+ if (event.target.value.length > 13) return
+
+ event.target.value = event.target.value
+ .replace(/[^0-9]/g, '')
+ .replace(/^(\d{0,3})(\d{0,4})(\d{0,4})$/g, '$1-$2-$3').replace(/(-{1,2})$/g, '')
+
+ onChange(event);
+ clearErrors('hostPhoneNumber')
+ }}
onBlur={() => {
onBlur();
- setHasBlurred((prev) => ({ ...prev, hostPhoneNumber: true }));
+
+ if (!value) {
+ setError('hostPhoneNumber', { type: 'required', message: '필수 입력사항입니다.' });
+ return
+ }
+
+ if (!phoneNumberRegExp.test(value)) {
+ setError('hostPhoneNumber', { type: 'pattern', message: '유효한 전화번호 형식이 아닙니다.' });
+ return
+ }
}}
value={value ?? ''}
- errorMessage={
- hasBlurred.hostPhoneNumber && !value ? '필수 입력사항입니다.' : undefined
- }
+ errorMessage={errors.hostPhoneNumber?.message}
/>
)}
name="hostPhoneNumber"
/>
-
+
);
};
diff --git a/apps/admin/src/components/ShowInfoFormContent/ShowInfoFormContent.styles.ts b/apps/admin/src/components/ShowInfoFormContent/ShowInfoFormContent.styles.ts
index da0a2fe6..d50ac25a 100644
--- a/apps/admin/src/components/ShowInfoFormContent/ShowInfoFormContent.styles.ts
+++ b/apps/admin/src/components/ShowInfoFormContent/ShowInfoFormContent.styles.ts
@@ -75,6 +75,22 @@ const ShowInfoFormRow = styled.div`
}
`;
+const ShowInfoFormResponsiveRowColumn = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 28px;
+ margin-bottom: 28px;
+
+ ${mq_lg} {
+ flex-direction: row;
+ gap: 12px;
+ }
+
+ &:last-of-type {
+ margin-bottom: 0;
+ }
+`
+
const ShowInfoFormContent = styled.div`
flex: 1;
`;
@@ -98,9 +114,21 @@ const ShowInfoFormDescription = styled.p`
color: ${({ theme }) => theme.palette.grey.g60};
margin-top: 2px;
+ span {
+ display: inline-block;
+ width: 100%;
+ }
+
strong {
font-weight: 600;
}
+
+ ${mq_lg} {
+ span {
+ display: inline;
+ width: auto;
+ }
+ }
`;
const ShowInfoFormButtonContainer = styled.div`
@@ -108,27 +136,27 @@ const ShowInfoFormButtonContainer = styled.div`
gap: 8px;
`;
-const ShowInfoFormButton = styled(Button)`
+const ShowInfoFormButton = styled(Button) `
width: ${({ width }) => width};
`;
const PreviewImageContainer = styled.div`
display: grid;
- grid-template-columns: repeat(3, 88px);
+ grid-template-columns: repeat(3, 1fr);
gap: 8px;
- height: 124px;
margin-top: 16px;
+ aspect-ratio: 562 / 256;
${mq_lg} {
- grid-template-columns: repeat(3, 1fr);
gap: 28px;
height: 256px;
+ aspect-ratio: initial;
}
`;
const PreviewImage = styled.div<{ isFirstImage: boolean }>`
max-width: 100%;
- height: ${({ isFirstImage }) => (isFirstImage ? 'calc(124px - 16px)' : '124px')};
+ height: ${({ isFirstImage }) => (isFirstImage ? 'calc(100% - 16px)' : '100%')};
width: 100%;
background-size: cover;
background-repeat: no-repeat;
@@ -144,6 +172,7 @@ const PreviewImageWrap = styled.div<{ isFirstImage: boolean }>`
position: relative;
border-radius: 4px;
border: 1px solid ${({ theme }) => theme.palette.grey.g20};
+ aspect-ratio: 182 / 256;
`;
const FirstImageText = styled.span`
@@ -247,7 +276,7 @@ const FileUploadAreaText = styled.span`
const TextField = styled.div`
margin-top: 8px;
display: flex;
- align-items: start;
+ align-items: center;
gap: 8px;
flex: ${({ flex }) => flex};
@@ -275,7 +304,7 @@ const TextArea = styled.textarea`
padding: 12px;
border: 1px solid
${({ theme, hasError }) =>
- hasError ? `${theme.palette.status.error} !important` : theme.palette.grey.g20};
+ hasError ? `${theme.palette.status.error} !important` : theme.palette.grey.g20};
border-radius: 4px;
background-color: ${({ theme }) => theme.palette.grey.w};
color: ${({ theme }) => theme.palette.grey.g90};
@@ -527,7 +556,7 @@ const MobileTicketAction = styled.div`
width: 24px;
height: 24px;
stroke: ${({ theme, disabled }) =>
- disabled ? theme.palette.grey.g40 : theme.palette.grey.g90};
+ disabled ? theme.palette.grey.g40 : theme.palette.grey.g90};
}
}
}
@@ -541,6 +570,7 @@ const MobileCastInfoRegisterButton = styled.button`
display: inline-flex;
justify-content: center;
align-items: center;
+ gap: 4px;
${({ theme }) => theme.typo.sh1};
color: ${({ theme }) => theme.palette.grey.g90};
cursor: pointer;
@@ -564,6 +594,7 @@ export default {
ShowInfoFormTitle,
ShowInfoFormSubtitle,
ShowInfoFormRow,
+ ShowInfoFormResponsiveRowColumn,
ShowInfoFormContent,
ShowInfoFormLabel,
ShowInfoFormDescription,
diff --git a/apps/admin/src/components/ShowInfoFormContent/ShowTicketInfoFormContent.tsx b/apps/admin/src/components/ShowInfoFormContent/ShowTicketInfoFormContent.tsx
index 1e54673c..0910f375 100644
--- a/apps/admin/src/components/ShowInfoFormContent/ShowTicketInfoFormContent.tsx
+++ b/apps/admin/src/components/ShowInfoFormContent/ShowTicketInfoFormContent.tsx
@@ -1,16 +1,15 @@
import { TextField } from '@boolti/ui';
import { format, sub } from 'date-fns';
-import { useState } from 'react';
import { Controller, UseFormReturn } from 'react-hook-form';
import Styled from './ShowInfoFormContent.styles';
import { ShowTicketFormInputs } from './types';
-
-type ShowTicketFormRequiredInputs = Omit;
+import { useCallback, useEffect } from 'react';
interface ShowTicketInfoFormContentProps {
form: UseFormReturn;
showDate: string;
+ showCreatedAt?: string;
salesStartTime?: string;
disabled?: boolean;
}
@@ -18,26 +17,79 @@ interface ShowTicketInfoFormContentProps {
const ShowTicketInfoFormContent = ({
form,
showDate,
+ showCreatedAt,
salesStartTime,
disabled,
}: ShowTicketInfoFormContentProps) => {
- const { watch, control } = form;
+ const { watch, control, formState: { errors }, setError, clearErrors } = form;
- const [hasBlurred, setHasBlurred] = useState>(
- {
- startDate: false,
- endDate: false,
- },
- );
+ const minStartDate = format(showCreatedAt ?? new Date(), 'yyyy-MM-dd')
+ const minEndDate = format(
+ watch('startDate') ||
+ (salesStartTime ? new Date(salesStartTime) : new Date()),
+ 'yyyy-MM-dd',
+ )
+ const maxDate = format(
+ sub(showDate ? new Date(showDate) : new Date(), { days: 1 }),
+ 'yyyy-MM-dd',
+ )
+
+ const validateStartDate = useCallback((value: string) => {
+ if (!value) {
+ setError('startDate', { type: 'required', message: '필수 입력사항입니다.' });
+ return
+ }
+
+ if (new Date(value) > new Date(maxDate)) {
+ setError('startDate', { type: 'max', message: '공연일 이전까지 선택 가능합니다.' });
+ return
+ }
+
+ if (new Date(value) < new Date(minStartDate)) {
+ const message = showCreatedAt ? `공연 등록일부터 선택 가능합니다. (${format(showCreatedAt, 'yy.MM.dd')})` : '오늘부터 선택 가능합니다.';
+ setError('startDate', { type: 'min', message });
+ return
+ }
+
+ clearErrors('startDate')
+ }, [clearErrors, maxDate, minStartDate, setError, showCreatedAt])
+
+ const validateEndDate = useCallback((value: string) => {
+ if (!value) {
+ setError('endDate', { type: 'required', message: '필수 입력사항입니다.' });
+ return
+ }
+
+ if (new Date(value) > new Date(maxDate)) {
+ setError('endDate', { type: 'max', message: '공연일 이전까지 선택 가능합니다.' });
+ return
+ }
+
+ if (new Date(value) < new Date(minEndDate)) {
+ setError('endDate', { type: 'min', message: '시작일부터 선택 가능합니다.' });
+ return
+ }
+
+ clearErrors('endDate')
+ }, [clearErrors, maxDate, minEndDate, setError])
+
+ useEffect(() => {
+ if (!watch('startDate') || !watch('endDate')) return;
+
+ validateStartDate(watch('startDate'));
+ validateEndDate(watch('endDate'));
+ }, [validateEndDate, validateStartDate, watch])
return (
- 티켓 판매 정보
+
+ 티켓 판매
+
-
+
- 판매 시작일
+ 시작일
{
+ onChange(event);
+ validateStartDate(event.target.value);
+ }}
onBlur={() => {
onBlur();
- setHasBlurred((prev) => ({ ...prev, startDate: true }));
+ validateStartDate(value);
}}
+ value={value}
placeholder={value}
- min={format(salesStartTime ?? new Date(), 'yyyy-MM-dd')}
- max={format(
- sub(showDate ? new Date(showDate) : new Date(), { days: 1 }),
- 'yyyy-MM-dd',
- )}
+ min={minStartDate}
+ max={maxDate}
required
disabled={disabled}
- errorMessage={
- hasBlurred.startDate && !value ? '필수 입력사항입니다.' : undefined
- }
+ errorMessage={errors.startDate?.message}
/>
)}
name="startDate"
@@ -71,7 +122,7 @@ const ShowTicketInfoFormContent = ({
- 판매 종료일
+ 종료일
{
+ onChange(event);
+ validateEndDate(event.target.value);
+ }}
onBlur={() => {
onBlur();
- setHasBlurred((prev) => ({ ...prev, endDate: true }));
+ validateEndDate(value);
}}
+ value={value}
placeholder={value}
- min={format(
- watch('startDate') ||
- (salesStartTime ? new Date(salesStartTime) : new Date()),
- 'yyyy-MM-dd',
- )}
- max={format(
- sub(showDate ? new Date(showDate) : new Date(), { days: 1 }),
- 'yyyy-MM-dd',
- )}
+ min={minEndDate}
+ max={maxDate}
required
disabled={disabled}
- errorMessage={
- hasBlurred.endDate && !value ? '필수 입력사항입니다.' : undefined
- }
+ errorMessage={errors.endDate?.message}
/>
)}
name="endDate"
/>
-
+
diff --git a/apps/admin/src/components/TicketForm/InvitationTicketForm.tsx b/apps/admin/src/components/TicketForm/InvitationTicketForm.tsx
index a7bc7dee..f28e909d 100644
--- a/apps/admin/src/components/TicketForm/InvitationTicketForm.tsx
+++ b/apps/admin/src/components/TicketForm/InvitationTicketForm.tsx
@@ -21,6 +21,7 @@ const InvitationTicketForm = ({ onSubmit }: InvitationTicketFormProps) => {
formState: { isDirty, isValid },
} = useForm();
+ // TODO: react-hook-form의 에러 기능을 사용하도록 수정
const [hasBlurred, setHasBlurred] = useState>({
name: false,
totalForSale: false,
diff --git a/apps/admin/src/components/TicketForm/SalesTicketForm.tsx b/apps/admin/src/components/TicketForm/SalesTicketForm.tsx
index d4b8f1fc..a0bea5a7 100644
--- a/apps/admin/src/components/TicketForm/SalesTicketForm.tsx
+++ b/apps/admin/src/components/TicketForm/SalesTicketForm.tsx
@@ -22,6 +22,7 @@ const SalesTicketForm = ({ onSubmit }: SalesTicketFormProps) => {
formState: { isDirty, isValid },
} = useForm();
+ // TODO: react-hook-form의 에러 기능을 사용하도록 수정
const [hasBlurred, setHasBlurred] = useState>({
name: false,
price: false,
diff --git a/apps/admin/src/pages/HomePage/HomePage.styles.ts b/apps/admin/src/pages/HomePage/HomePage.styles.ts
index 8a9319ec..27500ca6 100644
--- a/apps/admin/src/pages/HomePage/HomePage.styles.ts
+++ b/apps/admin/src/pages/HomePage/HomePage.styles.ts
@@ -48,8 +48,15 @@ const Container = styled.main`
}
`;
+const BannerContainer = styled.div`
+ border-bottom: 1px solid #C5E1FF;
+`
+
const Banner = styled.div`
- padding: 16px 0;
+ max-width: ${({ theme }) => theme.breakpoint.desktop};
+ margin: 0 auto;
+ padding: 16px 20px;
+ width: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
@@ -108,6 +115,7 @@ export default {
ProfileDropdown,
ProfileDropdownMobile,
Container,
+ BannerContainer,
Banner,
BannerShowTitle,
BannerDescription,
diff --git a/apps/admin/src/pages/HomePage/index.tsx b/apps/admin/src/pages/HomePage/index.tsx
index 4151bace..7a33bda9 100644
--- a/apps/admin/src/pages/HomePage/index.tsx
+++ b/apps/admin/src/pages/HomePage/index.tsx
@@ -120,15 +120,17 @@ const HomePage = () => {
settlementBanners &&
settlementBanners.length > 0 &&
settlementBanners?.map((banner) => (
-
-
- ‘{banner.showName}’{' '}
- {bannerDescription[banner.bannerType]}
-
-
- 정산 내역서 보러 가기
-
-
+
+
+
+ ‘{banner.showName}’{' '}
+ {bannerDescription[banner.bannerType]}
+
+
+ 정산 내역서 보러 가기
+
+
+
))
}
>
diff --git a/apps/admin/src/pages/ShowAddCompletePage/index.tsx b/apps/admin/src/pages/ShowAddCompletePage/index.tsx
index 221b4383..d06df61e 100644
--- a/apps/admin/src/pages/ShowAddCompletePage/index.tsx
+++ b/apps/admin/src/pages/ShowAddCompletePage/index.tsx
@@ -6,26 +6,30 @@ import congratulationSvgUrl from '~/assets/svg/congratulation.svg';
import { PATH } from '~/constants/routes';
import Styled from './ShowAddCompletePage.styles';
+import { checkIsWebView } from '~/utils/webview';
const ShowAddCompletePage = () => {
const navigate = useNavigate();
+ const isWebView = checkIsWebView(window.navigator.userAgent);
return (
<>
-
-
- {
- navigate(PATH.HOME);
- }}
- >
-
-
- 홈
-
-
+ {!isWebView && (
+
+
+ {
+ navigate(PATH.HOME);
+ }}
+ >
+
+
+ 홈
+
+
+ )}
diff --git a/apps/admin/src/pages/ShowAddPage/index.tsx b/apps/admin/src/pages/ShowAddPage/index.tsx
index 3bf48dfc..ef8c1fef 100644
--- a/apps/admin/src/pages/ShowAddPage/index.tsx
+++ b/apps/admin/src/pages/ShowAddPage/index.tsx
@@ -26,6 +26,7 @@ import Styled from './ShowAddPage.styles';
import ShowCastInfoFormContent from '~/components/ShowInfoFormContent/ShowCastInfoFormContent';
import ShowCastInfo from '~/components/ShowCastInfo';
import { TempShowCastInfoFormInput } from '~/components/ShowCastInfoFormDialogContent';
+import { checkIsWebView } from '~/utils/webview';
interface ShowAddPageProps {
step: 'info' | 'ticket';
@@ -33,6 +34,7 @@ interface ShowAddPageProps {
const ShowAddPage = ({ step }: ShowAddPageProps) => {
const navigate = useNavigate();
+ const isWebView = checkIsWebView(window.navigator.userAgent);
const [imageFiles, setImageFiles] = useState([]);
const [salesTicketList, setSalesTicketList] = useState([]);
@@ -104,19 +106,21 @@ const ShowAddPage = ({ step }: ShowAddPageProps) => {
return (
<>
-
-
- {
- navigate(PATH.HOME);
- }}
- >
-
-
- 홈
-
-
+ {!isWebView && (
+
+
+ {
+ navigate(PATH.HOME);
+ }}
+ >
+
+
+ 홈
+
+
+ )}
{step === 'info' && (
@@ -138,7 +142,7 @@ const ShowAddPage = ({ step }: ShowAddPageProps) => {
등록하려는 공연의 정보를 입력해 주세요.
- 공연 정보는 공연일 하루 전까지 수정할 수 있어요.
+ 입력한 정보는 등록 후에도 수정할 수 있어요.
@@ -348,7 +352,7 @@ const ShowAddPage = ({ step }: ShowAddPageProps) => {
등록하려는 공연의 정보를 입력해 주세요.
- 공연 정보는 공연일 하루 전까지 수정할 수 있어요.
+ 입력한 정보는 등록 후에도 수정할 수 있어요.
@@ -374,6 +378,34 @@ const ShowAddPage = ({ step }: ShowAddPageProps) => {
+
+ {
+ setShowCastInfo((prev) => [...prev, showCastInfoFormInput]);
+ return new Promise((reslve) => reslve());
+ }}
+ />
+ {showCastInfo.map((info, index) => (
+ {
+ setShowCastInfo((prev) =>
+ prev.map((prevCastInfo, currentIndex) =>
+ index === currentIndex ? showCastInfoFormInput : prevCastInfo,
+ ),
+ );
+ return new Promise((reslve) => reslve());
+ }}
+ onDelete={() => {
+ setShowCastInfo((prev) =>
+ prev.filter((_, currentIndex) => index !== currentIndex),
+ );
+ return new Promise((reslve) => reslve());
+ }}
+ />
+ ))}
+