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
5,380 changes: 5,380 additions & 0 deletions src/assets/lottie/confeti.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ const DrawingMain = ({
return (
<Drawing
winner={winner}
isLoading={isLoading}
onReExecuteRaffle={onExecuteRaffle}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,12 @@ export const buttonContainer = style({
marginTop: '2rem',
justifyContent: 'center',
});

export const confetiLottie = style({
width: '100%',
height: '100%',
padding: '2.4rem',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
76 changes: 54 additions & 22 deletions src/pages/admin/ticket-drawing/components/drawing/drawing.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import Loading from '@shared/components/loading/loading';
import Lottie from 'lottie-react';
import { useEffect, useState } from 'react';

import InfoSection from '@pages/admin/ticket-drawing/components/info-section/info-section';
import type { WinnerData } from '@pages/admin/ticket-drawing/types/winner-data';

import Title from '@shared/components/title/title';

import * as styles from './drawing.css';
import type { WinnerData } from '../../types/winner-data';
import InfoSection from '../info-section/info-section';
import confetiLottieAnimation from '../../../../../assets/lottie/confeti.json';

interface DrawingProps {
winner: WinnerData | null;
isLoading: boolean;
onReExecuteRaffle: () => void;
}

const Drawing = ({ winner, isLoading, onReExecuteRaffle }: DrawingProps) => {
const Drawing = ({ winner, onReExecuteRaffle }: DrawingProps) => {
const [isConfetiPlaying, setIsConfetiPlaying] = useState(false);
const [animationKey, setAnimationKey] = useState(0);

const animationData = confetiLottieAnimation;

useEffect(() => {
if (winner) {
setIsConfetiPlaying(false);
setAnimationKey((prev) => prev + 1);

setTimeout(() => {
setIsConfetiPlaying(true);
}, 100);
}
}, [winner]);

const handleReExecuteRaffle = () => {
setIsConfetiPlaying(false);
setAnimationKey((prev) => prev + 1);

setTimeout(() => {
setIsConfetiPlaying(true);
}, 100);

onReExecuteRaffle();
};

return (
<>
<div className={styles.title}>
Expand All @@ -20,24 +50,26 @@ const Drawing = ({ winner, isLoading, onReExecuteRaffle }: DrawingProps) => {
subTitle="응모권 당첨자 페이지입니다."
/>
</div>

{isLoading ? (
<Loading
size="medium"
message="추첨 중입니다..."
/>
) : (
<>
<div className={styles.container}>
<p className={styles.text}>당첨자</p>
<InfoSection
value={winner?.name || ''}
studentnumber={winner?.studentId || ''}
handleButtonClick={onReExecuteRaffle}
<>
<div className={styles.container}>
<p className={styles.text}>당첨자</p>
<InfoSection
value={winner?.name || ''}
studentnumber={winner?.studentId || ''}
handleButtonClick={handleReExecuteRaffle}
/>
</div>
<div className={styles.confetiLottie}>
{isConfetiPlaying && (
<Lottie
key={animationKey}
animationData={animationData}
autoPlay={true}
loop={false}
/>
</div>
</>
)}
)}
</div>
</>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ const InputSection = ({
onKeyChange,
}: InputSectionProps) => {
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
const koreanOnlyValue = value.replace(/[^ㄱ-ㅎㅏ-ㅣ가-힣·]/g, '');
onNameChange(koreanOnlyValue);
onNameChange(e.target.value);
};

const handleStudentNumChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down
23 changes: 18 additions & 5 deletions src/pages/ticket/components/modal-type/error-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,30 @@ import Modal from '@shared/components/modal/modal';

interface ErrorModalProps {
onClose: () => void;
isNameError?: boolean;
}

const ErrorModal = ({ onClose }: ErrorModalProps) => {
const ErrorModal = ({ onClose, isNameError = false }: ErrorModalProps) => {
return (
<Modal.Content>
<Modal.Body>
<Modal.Title>인증키 불일치</Modal.Title>
<Modal.Title>
{isNameError ? '입력 정보 오류' : '인증키 불일치'}
</Modal.Title>
<Modal.Description>
유효하지 않은 인증키 입니다.
<br />
다시 시도해 주세요.
{isNameError ? (
<>
이름과 학번을 다시 확인해주세요.
<br />
이름은 한글만 입력 가능합니다.
</>
) : (
<>
유효하지 않은 인증키 입니다.
<br />
다시 시도해 주세요.
</>
)}
</Modal.Description>
</Modal.Body>
<Modal.Footer>
Expand Down
9 changes: 8 additions & 1 deletion src/pages/ticket/components/ticketmodal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import PremiumModal from './modal-type/premium-modal';
import SuccessModal from './modal-type/success-modal';

interface TicketModalProps {
modalType: 'confirm' | 'success' | 'error' | 'premium' | null;
modalType: 'confirm' | 'success' | 'error' | 'nameError' | 'premium' | null;
name: string;
studentNumber: string;
onClose: () => void;
Expand Down Expand Up @@ -41,6 +41,13 @@ const TicketModal = ({
);
case 'error':
return <ErrorModal onClose={onClose} />;
case 'nameError':
return (
<ErrorModal
onClose={onClose}
isNameError={true}
/>
);
case 'premium':
return <PremiumModal {...sharedProps} />;
default:
Expand Down
21 changes: 17 additions & 4 deletions src/pages/ticket/hooks/use-ticket-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ interface TicketForm {
key: string;
}

type ModalType = 'confirm' | 'success' | 'error' | 'premium' | null;
type ModalType =
| 'confirm'
| 'success'
| 'error'
| 'nameError'
| 'premium'
| null;

export const useTicketForm = () => {
const queryClient = useQueryClient();
Expand All @@ -40,7 +46,7 @@ export const useTicketForm = () => {
TICKET_MUTATION_OPTIONS.MEMBER_LEVEL_UP(),
);

const isErrorState = modalType === 'error';
const isErrorState = modalType === 'error' || modalType === 'nameError';

useEffect(() => {
if (memberRaffleProfile?.result) {
Expand Down Expand Up @@ -81,11 +87,18 @@ export const useTicketForm = () => {

const handleConfirm = useCallback(async () => {
try {
// 한글 이름 유효성 검사
const koreanNameRegex = /^[ㄱ-ㅎㅏ-ㅣ가-힣\s]+$/;
if (!koreanNameRegex.test(form.name.trim())) {
setModalType('nameError');
return;
}

// UI level을 서버 level로 변환 (UI level과 동일)
const serverLevel = selectedLevel;

const response = await levelUpMutation.mutateAsync({
name: form.name,
name: form.name.trim(),
studentId: form.studentNum,
authenticationKey: form.key,
level: serverLevel,
Expand All @@ -99,7 +112,7 @@ export const useTicketForm = () => {
// Lv.3 응모 시 자동으로 Lv.4도 응모
if (selectedLevel === 3) {
await levelUpMutation.mutateAsync({
name: form.name,
name: form.name.trim(),
studentId: form.studentNum,
authenticationKey: form.key,
level: 4, // Lv.4
Expand Down