Skip to content

Commit

Permalink
Merge pull request #208 from Nexters/fix/desktop-cast-info-qa-issue
Browse files Browse the repository at this point in the history
fix: 데스크탑 출연진 등록 QA 이슈 수정
  • Loading branch information
alstn2468 authored Oct 7, 2024
2 parents 3140324 + 52f46d5 commit ae5da7a
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const Rolename = styled.span`
text-overflow: ellipsis;
overflow: hidden;
flex: 0 1 auto;
max-width: 100px;
`;

const CollapseButton = styled.button`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface ShowInfoFormLabelProps {

interface InputWrapperProps {
text: string;
isError?: boolean;
}

const ShowInfoFormLabel = styled.span<ShowInfoFormLabelProps>`
Expand All @@ -32,19 +33,30 @@ const MemberList = styled.div`
}
`;

const FieldWrap = styled.div`
flex: 1;
margin-right: 8px;
width: calc(50% - 32px);
`;

const InputWrapper = styled.div<InputWrapperProps>`
${({ theme }) => theme.typo.b3};
border: 1px solid ${({ text, theme }) => (text ? theme.palette.grey.g90 : theme.palette.grey.g20)};
border: 1px solid
${({ text, theme, isError }) =>
isError
? theme.palette.status.error
: text
? theme.palette.grey.g90
: theme.palette.grey.g20};
border-radius: 4px;
background-color: ${({ theme }) => theme.palette.grey.w};
padding: 8px 12px;
height: 48px;
margin-right: 8px;
flex: auto;
position: relative;
display: flex;
align-items: center;
width: calc(50% - 32px);
width: 100%;
`;

const TextFieldWrap = styled.div`
Expand All @@ -62,7 +74,7 @@ const HashTag = styled.span`
`;

const Input = styled.input`
width: ${({ value }) => (value ? 'calc(100% - 80px)' : '100%')};
width: 100%;
line-height: 24px;
&::placeholder {
Expand All @@ -73,16 +85,18 @@ const Input = styled.input`
const Row = styled.div`
display: flex;
justify-content: center;
align-items: center;
align-items: flex-start;
margin-bottom: 20px;
`;

const TrashCanButton = styled.button`
margin-top: 10px;
cursor: pointer;
height: 100%;
`;

const MemberAddButton = styled.button`
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
Expand Down Expand Up @@ -141,6 +155,11 @@ const ButtonWrap = styled.div`
margin-top: 32px;
`;

const ErrorMessage = styled.span`
${({ theme }) => theme.typo.b1};
color: ${({ theme }) => theme.palette.status.error};
`;

export default {
ShowInfoFormLabel,
InputWrapper,
Expand All @@ -157,4 +176,6 @@ export default {
TextFieldWrap,
ButtonWrap,
DeleteButton,
ErrorMessage,
FieldWrap,
};
209 changes: 126 additions & 83 deletions apps/admin/src/components/ShowCastInfoFormDialogContent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Styled from './ShowCastInfoFormDialogContent.styles';
import { useState } from 'react';
import { useBodyScrollLock } from '~/hooks/useBodyScrollLock';
import { ClearIcon, PlusIcon, TrashIcon } from '@boolti/icon';
import { Member, ShowCastTeamCreateOrUpdateRequest, queryKeys, useQueryClient } from '@boolti/api';
import { Member, queryKeys, useQueryClient } from '@boolti/api';
import { replaceUserCode } from '~/utils/replace';

export interface TempShowCastInfoFormInput {
Expand Down Expand Up @@ -32,6 +32,7 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P
const { fields, append, remove, update } = useFieldArray({
control,
name: 'members',
keyName: '_id',
});
const watchMemberFields = watch('members') ?? [];
const controlledFields = fields.map((field, index) => {
Expand All @@ -48,12 +49,14 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P

useBodyScrollLock(true);

const [hasBlurred, setHasBlurred] = useState<
Record<keyof ShowCastTeamCreateOrUpdateRequest, boolean | boolean[]>
>({
name: false,
members: [],
});
const [isNameFieldBlurred, setIsNameFieldBlurred] = useState(false);
const [isMemberFieldBlurred, setIsMemberFieldBlurred] = useState<
Array<{ userCode: boolean; roleName: boolean }>
>(
prevShowCastInfo?.members
? prevShowCastInfo.members.map(() => ({ userCode: false, roleName: false }))
: [{ userCode: false, roleName: false }],
);

return (
<>
Expand All @@ -73,100 +76,130 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P
onChange={onChange}
onBlur={() => {
onBlur();
setHasBlurred((prev) => ({ ...prev, name: true }));
setIsNameFieldBlurred(true);
}}
value={value ?? ''}
errorMessage={hasBlurred.name && !value ? '필수 입력사항입니다.' : undefined}
errorMessage={isNameFieldBlurred && !value ? '필수 입력사항입니다.' : undefined}
/>
</Styled.TextFieldWrap>
)}
name="name"
/>
<Styled.ShowInfoFormLabel>팀원</Styled.ShowInfoFormLabel>
<Styled.MemberList>
{controlledFields.map((controlledField, index) => (
<Styled.Row key={controlledField.id}>
{fields.map((field, index) => (
<Styled.Row key={field._id}>
<Controller
control={control}
defaultValue={controlledField.userCode}
render={({ field: { onChange, onBlur } }) => (
<Styled.InputWrapper text={controlledField.userCode ?? ''}>
{controlledField.userImgPath && controlledField.userNickname ? (
<>
<Styled.UserImage
style={
{
'--imgPath': `url(${controlledField.userImgPath})`,
} as React.CSSProperties
}
/>
<Styled.Username>{controlledField.userNickname}</Styled.Username>
<Styled.RemoveButton
onClick={() => {
update(index, { roleName: controlledField.roleName });
}}
>
<ClearIcon />
</Styled.RemoveButton>
</>
) : (
<>
<Styled.HashTag>#</Styled.HashTag>
<Styled.Input
placeholder="식별 코드"
required
onChange={(e) => {
const nextValue = replaceUserCode(e.target.value);
onChange(nextValue);
}}
onBlur={async (event) => {
onBlur();
const userCode = event.target.value;
if (userCode !== '') {
try {
const { imgPath, nickname } = await queryClient.fetchQuery(
queryKeys.user.userCode(event.target.value),
);
update(index, {
...controlledField,
userImgPath: imgPath,
userNickname: nickname,
});
} catch {
toast.error(
'불티에 회원으로 등록된 식별 코드로만 등록이 가능합니다.' +
'\n' +
'식별 코드를 확인 후 다시 시도해 주세요.',
);
defaultValue={field.userCode}
render={({ field: { onChange, onBlur, value } }) => {
const isError = Boolean(
isMemberFieldBlurred[index].userCode &&
value &&
(!field.userImgPath || !field.userNickname),
);
return (
<Styled.FieldWrap>
<Styled.InputWrapper text={value ?? ''} isError={isError}>
{field.userImgPath && field.userNickname ? (
<>
<Styled.UserImage
style={
{
'--imgPath': `url(${field.userImgPath})`,
} as React.CSSProperties
}
}
}}
value={controlledField.userCode ?? ''}
/>
</>
)}
</Styled.InputWrapper>
)}
/>
<Styled.Username>{field.userNickname}</Styled.Username>
<Styled.RemoveButton
onClick={() => {
update(index, { roleName: field.roleName });
}}
>
<ClearIcon />
</Styled.RemoveButton>
</>
) : (
<>
<Styled.HashTag>#</Styled.HashTag>
<Styled.Input
placeholder="식별 코드"
required
onChange={(e) => {
const nextValue = replaceUserCode(e.target.value);
onChange(nextValue);
}}
onBlur={async (event) => {
onBlur();
const userCode = event.target.value;
if (userCode !== '') {
try {
const { imgPath, nickname } = await queryClient.fetchQuery(
queryKeys.user.userCode(event.target.value),
);
update(index, {
...controlledFields[index],
userImgPath: imgPath,
userNickname: nickname,
});
} catch {
toast.error(
'불티에 회원으로 등록된 식별 코드로만 등록이 가능합니다.' +
'\n' +
'식별 코드를 확인 후 다시 시도해 주세요.',
);
} finally {
setIsMemberFieldBlurred((prev) => {
const nextMemberFieldBlurred = [...prev];
nextMemberFieldBlurred[index].userCode = true;
return nextMemberFieldBlurred;
});
}
}
}}
value={value ?? ''}
/>
</>
)}
</Styled.InputWrapper>
{isError && <Styled.ErrorMessage>필수 입력사항입니다.</Styled.ErrorMessage>}
</Styled.FieldWrap>
);
}}
name={`members.${index}.userCode`}
/>
<Controller
control={control}
rules={{
required: true,
}}
render={({ field: { onChange, onBlur } }) => (
<Styled.InputWrapper text={controlledField.roleName ?? ''}>
<Styled.Input
placeholder="역할"
required
onChange={onChange}
onBlur={() => {
onBlur();
}}
value={controlledField.roleName ?? ''}
/>
</Styled.InputWrapper>
)}
render={({ field: { onChange, onBlur, value } }) => {
const isError = isMemberFieldBlurred[index].roleName && !value;
return (
<Styled.FieldWrap>
<Styled.InputWrapper
text={value ?? ''}
isError={isMemberFieldBlurred[index].roleName && !value}
>
<Styled.Input
placeholder="역할"
required
onChange={onChange}
onBlur={() => {
onBlur();
setIsMemberFieldBlurred((prev) => {
const nextMemberFieldBlurred = [...prev];
nextMemberFieldBlurred[index].roleName = true;
return nextMemberFieldBlurred;
});
}}
value={value ?? ''}
/>
</Styled.InputWrapper>
{isError && <Styled.ErrorMessage>필수 입력사항입니다.</Styled.ErrorMessage>}
</Styled.FieldWrap>
);
}}
name={`members.${index}.roleName`}
/>
<Styled.TrashCanButton
Expand All @@ -178,6 +211,9 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P

if (isConfirm) {
toast.success('팀원 정보를 삭제했습니다.');
setIsMemberFieldBlurred((prev) =>
prev.filter((_, blurredIndex) => blurredIndex !== index),
);
remove(index);
}
}}
Expand All @@ -189,6 +225,7 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P
<Styled.MemberAddButton
onClick={() => {
append({});
setIsMemberFieldBlurred((prev) => [...prev, { userCode: false, roleName: false }]);
}}
>
<PlusIcon />
Expand Down Expand Up @@ -221,7 +258,13 @@ const ShowCastInfoFormDialogContent = ({ onDelete, prevShowCastInfo, onSave }: P
type="button"
colorTheme="primary"
size="bold"
disabled={disabled}
disabled={
disabled ||
controlledFields.some(
({ userImgPath, userNickname, roleName }) =>
!userImgPath || !userNickname || !roleName,
)
}
onClick={async (e) => {
e.preventDefault();

Expand Down
4 changes: 2 additions & 2 deletions apps/admin/src/pages/ShowInfoPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const ShowInfoPage = () => {
showId,
name,
members: members
?.filter(({ id, userCode, roleName }) => id && userCode && roleName)
?.filter(({ userCode, roleName }) => userCode && roleName)
.map(({ id, userCode, roleName }) => ({
id,
userCode,
Expand All @@ -239,7 +239,7 @@ const ShowInfoPage = () => {
{
name,
members: members
?.filter(({ id, userCode, roleName }) => id && userCode && roleName)
?.filter(({ userCode, roleName }) => userCode && roleName)
.map(({ id, userCode, roleName }) => ({
id,
userCode,
Expand Down

0 comments on commit ae5da7a

Please sign in to comment.