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
911 changes: 610 additions & 301 deletions .pnp.cjs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const nextConfig = {
return config;
},
experimental: {
reactCompiler: true,
workerThreads: false,
},
images: {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@types/react-window": "^1.8.5",
"babel-plugin-react-compiler": "1.0.0",
"dotenv": "^17.2.3",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
Expand All @@ -78,7 +79,7 @@
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-hooks": "^7.1.1",
"globals": "^16.4.0",
"jest": "^29.7.0",
"next-sitemap": "^4.2.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, useState } from 'react';
import { useState } from 'react';
import { cn } from '@bcsdlab/utils';
import ChevronLeftIcon from 'assets/svg/Articles/chevron-left.svg';
import ChevronRightIcon from 'assets/svg/Articles/chevron-right.svg';
Expand All @@ -25,15 +25,11 @@ interface CalendarProps {
export default function Calendar({ selectedDate, setSelectedDate }: CalendarProps) {
const today = new Date();
const [currentMonthDate, setCurrentMonthDate] = useState(selectedDate);
const days = useMemo(
() =>
Array.from({ length: 35 }, (_, i) => {
const startDate = new Date(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), 1);
startDate.setDate(startDate.getDate() - startDate.getDay());
return new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + i);
}),
[currentMonthDate],
);
const days = Array.from({ length: 35 }, (_, i) => {
const startDate = new Date(currentMonthDate.getFullYear(), currentMonthDate.getMonth(), 1);
startDate.setDate(startDate.getDate() - startDate.getDay());
return new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() + i);
});

const handleMonthChevronClick = (diff: number) => {
setCurrentMonthDate((prev) => new Date(prev.getFullYear(), prev.getMonth() + diff, 1));
Expand Down
15 changes: 7 additions & 8 deletions src/components/Articles/hooks/usePagination.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useMemo } from 'react';
import useParamsHandler from 'utils/hooks/routing/useParamsHandler';

const PAGE_LIMIT = 5;
Expand All @@ -9,14 +8,14 @@ const usePagination = (totalPageNum: number) => {
const raw = Number(params.page) || 1;
const currentPage = Math.min(Math.max(raw, 1), Math.max(totalPageNum, 1));

const pages = useMemo(() => {
const half = Math.floor(PAGE_LIMIT / 2);
const maxStart = Math.max(1, totalPageNum - PAGE_LIMIT + 1);
const startPage = Math.min(Math.max(1, currentPage - half), maxStart);
const length = Math.min(PAGE_LIMIT, totalPageNum);

return Array.from({ length }, (_, i) => startPage + i);
}, [currentPage, totalPageNum]);
const half = Math.floor(PAGE_LIMIT / 2);
const maxStart = Math.max(1, totalPageNum - PAGE_LIMIT + 1);
const startPage = Math.min(Math.max(1, currentPage - half), maxStart);
const length = Math.min(PAGE_LIMIT, totalPageNum);

const pages = Array.from({ length }, (_, i) => startPage + i);


const movePage = (target: number | 'prev' | 'next') => {
let nextPage = currentPage;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable no-restricted-imports */
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { isKoinError } from '@bcsdlab/koin';
import { useMutation } from '@tanstack/react-query';
import { checkPhone, smsSend, smsVerify } from 'api/auth';
Expand Down Expand Up @@ -52,6 +51,7 @@ function MobileVerification({ onNext }: MobileVerificationProps) {
const [isVerified, enableVerified] = useBooleanState(false);
const [isCodeCorrect, setCorrect, setIncorrect] = useBooleanState(false);
const [smsSendCountData, setSmsSendCountData] = useState<SmsSendCountData | null>(null);
const verificationMessageTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const {
isRunning: isTimer,
Expand All @@ -65,12 +65,24 @@ function MobileVerification({ onNext }: MobileVerificationProps) {
},
});

const clearVerificationMessageTimer = () => {
if (verificationMessageTimerRef.current) {
clearTimeout(verificationMessageTimerRef.current);
verificationMessageTimerRef.current = null;
}
};

const { mutate: sendSMSToUser } = useMutation({
mutationFn: smsSend,
onSuccess: (data: SmsSendResponse) => {
clearVerificationMessageTimer();
setPhoneMessage({ type: 'success', content: MESSAGES.PHONE.CODE_SENT });
runTimer();
showVerification();
clearVerificationMessageTimer();
verificationMessageTimerRef.current = setTimeout(() => {
setVerificationMessage({ type: 'default', content: MESSAGES.VERIFICATION.DEFAULT });
}, 60000);
Comment thread
ParkSungju01 marked this conversation as resolved.
setSmsSendCountData({
total_count: data.total_count,
remaining_count: data.remaining_count,
Expand Down Expand Up @@ -149,21 +161,20 @@ function MobileVerification({ onNext }: MobileVerificationProps) {
});
};

const isNameAndGenderFilled = name?.trim() && gender?.length > 0;

useEffect(() => {
const resetVerificationState = () => {
clearVerificationMessageTimer();
disableButton();
stopTimer();
setVerificationMessage(null);
setPhoneMessage(null);
setIncorrect();
}, [phoneNumber]);
};

const isNameAndGenderFilled = name?.trim() && gender?.length > 0;

useEffect(() => {
if (timerValue === 120) {
setVerificationMessage({ type: 'default', content: MESSAGES.VERIFICATION.DEFAULT });
}
}, [timerValue]);
return () => clearVerificationMessageTimer();
}, []);

return (
<div className={styles.container}>
Expand Down Expand Up @@ -208,6 +219,12 @@ function MobileVerification({ onNext }: MobileVerificationProps) {
render={({ field }) => (
<CustomInput
{...field}
onChange={(e) => {
if (e.target.value !== field.value) {
resetVerificationState();
}
field.onChange(e);
}}
placeholder="- 없이 번호를 입력해 주세요."
isDelete={!isVerified}
message={phoneMessage}
Expand Down
1 change: 1 addition & 0 deletions src/components/Auth/SignupPage/Steps/Terms/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use no memo'; //react compiler로 인한 watch 오류 방지
import { cn } from '@bcsdlab/utils';
import CustomCheckbox from 'components/Auth/SignupPage/components/CustomCheckbox';
import { useFormContext } from 'react-hook-form';
Expand Down
10 changes: 5 additions & 5 deletions src/components/Callvan/components/AddPostForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState } from 'react';
import { useState } from 'react';
import { useRouter } from 'next/router';
import { cn } from '@bcsdlab/utils';
import { CALLVAN_POST_LOCATION_LABEL, CallvanPostLocationType } from 'api/callvan/entity';
Expand Down Expand Up @@ -70,22 +70,22 @@ export default function AddPostForm() {
(form.departureType !== 'CUSTOM' || form.departureCustomName.trim() !== '') &&
(form.arrivalType !== 'CUSTOM' || form.arrivalCustomName.trim() !== '');

const handleSwapLocation = useCallback(() => {
const handleSwapLocation = () => {
setForm((prev) => ({
...prev,
departureType: prev.arrivalType,
departureCustomName: prev.arrivalCustomName,
arrivalType: prev.departureType,
arrivalCustomName: prev.departureCustomName,
}));
}, []);
}

const handleParticipantsChange = useCallback((delta: number) => {
const handleParticipantsChange = (delta: number) => {
setForm((prev) => ({
...prev,
maxParticipants: Math.min(11, Math.max(2, prev.maxParticipants + delta)),
}));
}, []);
}

const handleSubmit = () => {
if (!form.departureType || !form.arrivalType || isPending) return;
Expand Down
18 changes: 8 additions & 10 deletions src/components/Callvan/components/CallvanChatRoom/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState, useEffect, useMemo } from 'react';
import { useRef, useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useSuspenseQuery } from '@tanstack/react-query';
import { CallvanChatMessage } from 'api/callvan/entity';
Expand Down Expand Up @@ -112,15 +112,13 @@ export default function CallvanChatRoom({ postId }: CallvanChatRoomProps) {
}
};

const senderColorMap = useMemo(() => {
const map = new Map<number, number>();
data.messages.forEach((msg) => {
if (!msg.is_mine && !map.has(msg.user_id)) {
map.set(msg.user_id, map.size);
}
});
return map;
}, [data.messages]);
const senderColorMap = new Map<number, number>();

data.messages.forEach((message) => {
if (!message.is_mine && !senderColorMap.has(message.user_id)) {
senderColorMap.set(message.user_id, senderColorMap.size);
}
});

const messageGroups = groupMessagesByDate(data.messages);

Expand Down
30 changes: 8 additions & 22 deletions src/components/Callvan/components/CallvanFilterPanel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import {
CallvanAuthor,
CallvanLocation,
CallvanSort,
CallvanStatus,
CALLVAN_LOCATION_LABEL,
} from 'api/callvan/entity';
import { useEffect, useRef, useState } from 'react';
import { CallvanAuthor, CallvanLocation, CallvanSort, CallvanStatus, CALLVAN_LOCATION_LABEL } from 'api/callvan/entity';
import SpinIcon from 'assets/svg/Callvan/spin.svg';
import CloseIcon from 'assets/svg/close-icon-black.svg';
import StatusBadge from 'components/Callvan/components/StatusBadge';
Expand Down Expand Up @@ -81,28 +75,28 @@ export default function CallvanFilterPanel({
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onClose]);

const toggleStatus = useCallback((value: CallvanStatus) => {
const toggleStatus = (value: CallvanStatus) => {
setLocalStatuses((prev) => {
if (prev.includes(value)) return prev.filter((s) => s !== value);
return [...prev, value];
});
}, []);
};

const toggleDeparture = (value: CallvanLocation) => {
setLocalDepartures((prev) => {
if (prev.includes(value)) return prev.filter((l) => l !== value);
const next = [...prev, value];
return next.length === CALLVAN_FILTER_LOCATIONS.length ? [] : next;
});
}
};

const toggleArrival = (value: CallvanLocation) => {
setLocalArrivals((prev) => {
if (prev.includes(value)) return prev.filter((l) => l !== value);
const next = [...prev, value];
return next.length === CALLVAN_FILTER_LOCATIONS.length ? [] : next;
});
}
};

const handleApply = () => {
onApply({
Expand Down Expand Up @@ -191,11 +185,7 @@ export default function CallvanFilterPanel({
<section className={styles.section}>
<h3 className={styles.section__title}>모집 상태</h3>
<div className={styles.section__badges}>
<StatusBadge
label="전체"
isActive={localStatuses.length === 0}
onClick={() => setLocalStatuses([])}
/>
<StatusBadge label="전체" isActive={localStatuses.length === 0} onClick={() => setLocalStatuses([])} />
{STATUS_OPTIONS.map((opt) => (
<StatusBadge
key={opt.value}
Expand Down Expand Up @@ -239,11 +229,7 @@ export default function CallvanFilterPanel({
<span className={styles.section__description}>기타 장소는 검색창을 이용해주세요.</span>
</div>
<div className={styles.section__badges}>
<StatusBadge
label="전체"
isActive={localArrivals.length === 0}
onClick={() => setLocalArrivals([])}
/>
<StatusBadge label="전체" isActive={localArrivals.length === 0} onClick={() => setLocalArrivals([])} />
{CALLVAN_FILTER_LOCATIONS.map((loc) => (
<StatusBadge
key={loc}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Callvan/components/CallvanPageLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default function CallvanPageLayout({
[router],
);

const handleSearch = useCallback(() => {
const handleSearch = () => {
router.replace({
pathname: router.pathname,
query: {
Expand All @@ -102,7 +102,7 @@ export default function CallvanPageLayout({
page: undefined,
},
});
}, [router, title]);
};

return (
<div className={styles.layout}>
Expand Down
29 changes: 10 additions & 19 deletions src/components/Callvan/components/DateDropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { cn } from '@bcsdlab/utils';
import ChevronDownIcon from 'assets/svg/Callvan/chevron-down.svg';
import useBooleanState from 'utils/hooks/state/useBooleanState';
Expand All @@ -25,7 +25,7 @@ function ScrollColumn({ items, selectedIndex, onSelect }: ScrollColumnProps) {
ref.current.scrollTo({ top: selectedIndex * ITEM_HEIGHT, behavior: 'smooth' });
}, [selectedIndex]);

const handleScroll = useCallback(() => {
const handleScroll = () => {
if (!ref.current) return;
isScrollingRef.current = true;

Expand All @@ -38,19 +38,15 @@ function ScrollColumn({ items, selectedIndex, onSelect }: ScrollColumnProps) {
ref.current.scrollTo({ top: clamped * ITEM_HEIGHT, behavior: 'smooth' });
isScrollingRef.current = false;
}, 150);
}, [items.length, onSelect]);
};

const handleItemClick = (index: number) => {
onSelect(index);
ref.current?.scrollTo({ top: index * ITEM_HEIGHT, behavior: 'smooth' });
};

return (
<div
ref={ref}
className={styles['scroll-column']}
onScroll={handleScroll}
>
<div ref={ref} className={styles['scroll-column']} onScroll={handleScroll}>
<div className={styles['scroll-column__padding']} />
{items.map((item, i) => (
<div
Expand Down Expand Up @@ -137,23 +133,18 @@ export default function DateDropdown({ selectedDate, onChange }: DateDropdownPro
</button>

{isOpen && (
<div className={styles.dropdown} style={{ '--container-height': `${containerHeight}px` } as React.CSSProperties}>
<div
className={styles.dropdown}
style={{ '--container-height': `${containerHeight}px` } as React.CSSProperties}
>
<div className={styles.columns}>
<ScrollColumn
items={yearItems}
selectedIndex={Math.max(0, yearIndex)}
onSelect={(i) => setTempYear(years[i])}
/>
<ScrollColumn
items={monthItems}
selectedIndex={monthIndex}
onSelect={(i) => setTempMonth(i)}
/>
<ScrollColumn
items={dayItems}
selectedIndex={dayIndex}
onSelect={(i) => setTempDay(i + 1)}
/>
<ScrollColumn items={monthItems} selectedIndex={monthIndex} onSelect={(i) => setTempMonth(i)} />
<ScrollColumn items={dayItems} selectedIndex={dayIndex} onSelect={(i) => setTempDay(i + 1)} />
</div>

<div className={styles.divider} />
Expand Down
Loading
Loading