-
{activity.spotName}
+
+ {activity.spotName}
+
{translateDayTime(activity.dayTime)}
@@ -221,6 +247,10 @@ interface ActiveProp {
$active: boolean;
}
+interface History {
+ $isHistory?: string | null;
+}
+
const styles = {
wrapper: styled.div`
flex: 1 0 0;
@@ -322,21 +352,6 @@ const styles = {
align-items: center;
}
- .name {
- color: #7d7d7d;
- font-family: 'Noto Sans KR';
- font-size: 0.9375rem;
- font-style: normal;
- font-weight: 700;
- line-height: normal;
- letter-spacing: -0.01875rem;
-
- max-width: 50%;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
.dotted-line {
flex-grow: 1;
border-bottom: 1px dashed #d3d3d3;
@@ -354,6 +369,24 @@ const styles = {
}
`,
+ name: styled.p`
+ color: #7d7d7d;
+ font-family: 'Noto Sans KR';
+ font-size: 0.9375rem;
+ font-style: normal;
+ font-weight: 700;
+ line-height: normal;
+ letter-spacing: -0.01875rem;
+
+ background-color: ${(props) =>
+ props.$isHistory != null ? '#f5fca6' : 'transparent'};
+
+ max-width: 50%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ `,
+
pin: styled.img`
width: 0.3125rem;
height: 0.875rem;
diff --git a/src/components/profile/EditPasswordSection.tsx b/src/components/profile/EditPasswordSection.tsx
index 5b3a899..ae1484c 100644
--- a/src/components/profile/EditPasswordSection.tsx
+++ b/src/components/profile/EditPasswordSection.tsx
@@ -3,13 +3,13 @@
import styled from '@emotion/styled';
import { useState } from 'react';
-import { useChangePassword } from '@/features/member';
+import { putPassword } from '@/features/member/member.api';
+import { useToast } from '@/features/toast';
export function EditPasswordSection({ onClick }: { onClick: () => void }) {
const [oldPassword, setOldPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
-
- const { mutate: changePWD } = useChangePassword();
+ const { createToast } = useToast();
return (
@@ -37,8 +37,14 @@ export function EditPasswordSection({ onClick }: { onClick: () => void }) {
{
- changePWD({ oldPassword, newPassword });
- onClick();
+ putPassword(oldPassword, newPassword)
+ .then(() => {
+ createToast('success', '비밀번호가 변경되었습니다.');
+ })
+ .catch(() => {
+ createToast('error', '현재 비밀번호가 일치하지 않습니다.');
+ })
+ .finally(onClick);
}}
>
확인
diff --git a/src/components/profile/EditProfileSection.tsx b/src/components/profile/EditProfileSection.tsx
index 25ca97c..5b6c617 100644
--- a/src/components/profile/EditProfileSection.tsx
+++ b/src/components/profile/EditProfileSection.tsx
@@ -4,6 +4,7 @@ import styled from '@emotion/styled';
import { useState } from 'react';
import { useChangeUserName, type User } from '@/features/member';
+import { useToast } from '@/features/toast';
export function EditProfileSection({
user,
@@ -49,6 +50,7 @@ function ProfileInfo({
function ListItem({ menu, content }: { menu: string; content?: string }) {
const [value, setValue] = useState();
const { mutate: changeUserName } = useChangeUserName();
+ const { createToast } = useToast();
return (
{menu}
@@ -62,7 +64,13 @@ function ListItem({ menu, content }: { menu: string; content?: string }) {
{menu === '닉네임' && (
{
- if (value) changeUserName(value);
+ if (value)
+ changeUserName(value, {
+ onSuccess: () =>
+ createToast('success', '변경이 완료되었습니다.'),
+ onError: () =>
+ createToast('error', '변경하려는 이름이 너무 깁니다.'),
+ });
}}
>
변경하기
@@ -141,6 +149,7 @@ const styles = {
`,
changeButton: styled.button`
+ min-width: 3.33rem;
padding: 0.15rem 0.38rem;
border-radius: 0.25rem;
border: 0.4px solid #c9c9c9;
@@ -188,7 +197,7 @@ const styles = {
input {
border: none;
- width: 50%;
+ flex: 1;
height: 100%;
color: #b5b5b5;
text-align: right;
diff --git a/src/components/travel/ReviewCard.tsx b/src/components/travel/ReviewCard.tsx
index 5472cb2..a61507f 100644
--- a/src/components/travel/ReviewCard.tsx
+++ b/src/components/travel/ReviewCard.tsx
@@ -85,7 +85,6 @@ function ReviewInput({
{review ? (
<>
void;
onPrevPage: () => void;
}) {
- const { isLoading, tourInfo, recommendContent, fillActivities } =
+ const { isLoading, tourInfo, recommendContent, fillActivities, selectedDay } =
useTripStore();
const [isDetailVisible, setIsDetailVisible] = useState(false);
@@ -63,6 +64,17 @@ export function TravelerActivityRecommendation({
setIsDetailVisible(true);
};
+ const { createToast } = useToast();
+
+ useEffect(() => {
+ if (!isLoading)
+ createToast(
+ 'info',
+ '기존 계획에 특정 시간대가 추가되어있다면, 추천에서 해당 시간대를 추가하더라도 추가되지 않습니다.',
+ 5000,
+ );
+ }, [isLoading]);
+
return (
{!isDetailVisible && !isLoading && (
@@ -88,7 +100,10 @@ export function TravelerActivityRecommendation({
color='#FF75C8'
text='여행 완성'
onClick={() => {
+ if (!selectedDay) return;
+
fillActivities(
+ selectedDay,
selectedPlaces.map((place) => ({
dayTime: convertTimeString(place.time ?? '오전'),
orderIndex: 0,
diff --git a/src/components/travel/traveler/TravelerActivitySelection.tsx b/src/components/travel/traveler/TravelerActivitySelection.tsx
index e530c8c..65450bd 100644
--- a/src/components/travel/traveler/TravelerActivitySelection.tsx
+++ b/src/components/travel/traveler/TravelerActivitySelection.tsx
@@ -64,7 +64,7 @@ export function TravelerActivitySelection({
나머지 일정도 추천해드릴게요.
어떤 활동을 하고 싶으세요?
- {
setRecommendContent(value);
@@ -100,37 +100,6 @@ export function TravelerActivitySelection({
onNextPage();
}}
/>
- {
- setIsLoading(true);
- postDayTrip({
- tourDate: new Date().toISOString(),
- sigunguCode: String(
- types.regionType.find(
- (region) => region.type === tourInfo.locationName,
- )?.id ?? '1',
- ),
- contentTypeId: String(
- types.travelType.find(
- (region) => region.type === recommendContent,
- )?.id ?? '1',
- ),
- dayTimes: ['MORNING', 'AFTERNOON', 'EVENING', 'NIGHT'],
- })
- .then((res) => {
- setRecommendedItems(res.data);
- setIsLoading(false);
- })
- .catch(() => {
- createToast('error', '오류가 발생했습니다. 다시 시도해주세요.');
- onPrevPage();
- });
-
- onNextPage();
- }}
- />
);
}
@@ -185,4 +154,8 @@ const styles = {
width: 1rem;
height: 1rem;
`,
+
+ SearchBox: styled(SearchBox)`
+ margin-bottom: auto;
+ `,
};
diff --git a/src/components/travel/traveler/TravelerAddDays.tsx b/src/components/travel/traveler/TravelerAddDays.tsx
index 45a7556..0520bad 100644
--- a/src/components/travel/traveler/TravelerAddDays.tsx
+++ b/src/components/travel/traveler/TravelerAddDays.tsx
@@ -7,10 +7,21 @@ import { CustomButton } from '@/components';
import { TravelerLocationConfirm } from '@/components/travel/traveler/TravelerLocationConfirm';
import { TravelerLocationSearch } from '@/components/travel/traveler/TravelerLocationSearch';
import { useToast } from '@/features/toast';
-import { getTourSpots, type GetTourSpotsDTO } from '@/features/tour-spot';
-import { TIME_STRING } from '@/features/trip';
+import {
+ getTourSpotContents,
+ getTourSpots,
+ type GetTourSpotsDTO,
+} from '@/features/tour-spot';
+import { postDayTrip, TIME_STRING } from '@/features/trip';
import { useTripStore } from '@/features/trip/trip.slice';
+interface Location {
+ contentId: string;
+ contentTypeId: string;
+ title: string;
+ imageUrl: string;
+}
+
export function TravelerAddDays({
onPrevPage,
onNextPage,
@@ -33,10 +44,13 @@ export function TravelerAddDays({
removeTour,
addActivity,
removeActivity,
+ setIsLoading,
} = useTripStore();
const { createToast } = useToast();
+ const [locations, setLocations] = useState([]);
+
return (
<>
{state.ui === 'main' && (
@@ -112,18 +126,45 @@ export function TravelerAddDays({
)}
{state.ui === 'search' && (
{
if (!state.location) return;
- getTourSpots(state.location, tourInfo.sigunguCode ?? '').then(
- (res) => {
- setState((prev) => ({
- ...prev,
- ui: 'confirm',
- tourSpotDto: res,
- }));
- },
- );
+ setLocations([]);
+ setIsLoading(true);
+ getTourSpots(state.location, tourInfo.sigunguCode ?? '')
+ .then((res) => {
+ const { contentId, contentTypeId, sigunguCode } = res.data;
+
+ getTourSpotContents(contentId, contentTypeId)
+ .then((content) => ({ data: content.data }))
+ .then((contentData) => {
+ postDayTrip({
+ contentTypeId,
+ dayTimes: ['MORNING', 'AFTERNOON', 'EVENING', 'NIGHT'],
+ sigunguCode,
+ tourDate: new Date().toISOString(),
+ })
+ .then((recommend) => {
+ setLocations((prev) => [
+ ...prev,
+ ...recommend.data.filter(
+ (v) => v.contentId !== contentData.data.contentId,
+ ),
+ {
+ contentId: contentData.data.contentId,
+ contentTypeId: contentData.data.contentTypeId,
+ title: contentData.data.title,
+ imageUrl: contentData.data.firstImage,
+ },
+ ]);
+ })
+ .finally(() => setIsLoading(false));
+ });
+ })
+ .catch(() => {
+ setIsLoading(false);
+ });
}}
onContentChange={(value) =>
setState((prev) => ({ ...prev, location: value }))
@@ -134,6 +175,21 @@ export function TravelerAddDays({
ui: 'main',
}));
}}
+ onItemClick={(location) => {
+ setState((prev) => ({
+ ...prev,
+ ui: 'confirm',
+ tourSpotDto: {
+ status: 'success',
+ data: {
+ title: location.title,
+ contentId: location.contentId,
+ contentTypeId: location.contentTypeId,
+ sigunguCode: tourInfo.sigunguCode ?? '1',
+ },
+ },
+ }));
+ }}
/>
)}
{state.ui === 'confirm' &&
@@ -180,6 +236,7 @@ export function TravelerAddDays({
});
setState(() => ({ ui: 'main' }));
+ setLocations([]);
}}
onPrevPage={() => {
setState((prev) => ({ ...prev, ui: 'search' }));
diff --git a/src/components/travel/traveler/TravelerLocationSearch.tsx b/src/components/travel/traveler/TravelerLocationSearch.tsx
index e902d4f..9d9bf47 100644
--- a/src/components/travel/traveler/TravelerLocationSearch.tsx
+++ b/src/components/travel/traveler/TravelerLocationSearch.tsx
@@ -3,16 +3,33 @@
import styled from '@emotion/styled';
import { SearchBox } from '@/components';
+import { Loading } from '@/components/travel';
+import { useTripStore } from '@/features/trip/trip.slice';
+
+interface Location {
+ contentId: string;
+ contentTypeId: string;
+ title: string;
+ imageUrl: string;
+}
export function TravelerLocationSearch({
+ locations,
onClick,
onContentChange,
onPrevPage,
+ onItemClick,
}: {
+ locations: Location[];
onClick: () => void;
onContentChange: (value: string) => void;
onPrevPage: () => void;
+ onItemClick: (lcoation: Location) => void;
}) {
+ const { isLoading } = useTripStore();
+
+ if (isLoading) return ;
+
return (
@@ -23,11 +40,43 @@ export function TravelerLocationSearch({
/>
미리 계획한 장소를 입력하세요.
-
+
+ {locations.length > 0 ? (
+
+ {locations.map((location) => (
+ onItemClick(location)}
+ />
+ ))}
+
+ ) : (
+ 검색을 해주세요!
+ )}
);
}
+function LocationItem({
+ location: { title, imageUrl },
+ onClick,
+}: {
+ location: Location;
+ onClick: () => void;
+}) {
+ return (
+
+
+ {title}
+
+ );
+}
+
const styles = {
container: styled.div`
flex-grow: 1;
@@ -49,14 +98,46 @@ const styles = {
header: styled.div`
display: flex;
- position: fixed;
align-items: center;
gap: 0.5rem;
- transform: translate(-110%, 45%);
`,
prevButton: styled.img`
width: 1rem;
height: 1rem;
`,
+
+ results: styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ row-gap: 2rem;
+ `,
+
+ locationItem: styled.div`
+ width: calc(50% - 1rem);
+ height: 100px;
+
+ img {
+ width: 100%;
+ height: 90%;
+
+ background: #fff;
+ border-radius: 8px;
+ box-shadow: 0px 1px 4px 0px #6e80913d;
+
+ object-fit: cover;
+ object-position: center;
+ }
+ `,
+
+ empty: styled.p`
+ font-family: Noto Sans KR;
+ font-size: 1rem;
+ font-weight: 700;
+ line-height: 33.3px;
+ text-align: center;
+
+ color: #505050;
+ `,
};
diff --git a/src/components/travel/traveler/TravelerScheduleConfirm.tsx b/src/components/travel/traveler/TravelerScheduleConfirm.tsx
index 7c98baa..6824c31 100644
--- a/src/components/travel/traveler/TravelerScheduleConfirm.tsx
+++ b/src/components/travel/traveler/TravelerScheduleConfirm.tsx
@@ -3,11 +3,7 @@
import styled from '@emotion/styled';
import { useState } from 'react';
-import { TravelerLocationConfirm } from '@/components/travel/traveler/TravelerLocationConfirm';
-import { TravelerLocationSearch } from '@/components/travel/traveler/TravelerLocationSearch';
import { useToast } from '@/features/toast';
-import type { GetTourSpotsDTO } from '@/features/tour-spot';
-import { getTourSpots } from '@/features/tour-spot';
import {
getTripSchedule,
postTripSchedule,
@@ -19,177 +15,95 @@ import { useTripStore } from '@/features/trip/trip.slice';
export function TravelerScheduleConfirm({
onNextPage,
onPrevPage,
+ onRecommendPage,
}: {
onNextPage: () => void;
onPrevPage: () => void;
+ onRecommendPage: (day: number) => void;
}) {
- const [state, setState] = useState<{
- ui: 'main' | 'search' | 'confirm';
- day?: number;
- location?: string;
- time?: 'MORNING' | 'AFTERNOON' | 'EVENING' | 'NIGHT';
- tourSpotDto?: GetTourSpotsDTO;
- }>({ ui: 'main' });
+ const { tourInfo, activities, setIsLoading, load } = useTripStore();
- const { tourInfo, activities, setIsLoading, addActivity, load } =
- useTripStore();
+ const [enabled, setEnabled] = useState(true);
const { createToast } = useToast();
return (
- <>
- {state.ui === 'main' && (
-
-
-
-
- {tourInfo.locationName}
- {activities.map((acts, index) => (
- {
- setState((prev) => ({
- ...prev,
- ui: 'search',
- day: index + 1,
- }));
- }}
- />
- ))}
- {
- if (
- !tourInfo.name ||
- !tourInfo.locationName ||
- !tourInfo.startTime ||
- !tourInfo.endTime
- )
- return;
-
- if (activities.some((v) => v.length === 0)) {
- createToast('info', '장소를 한 개 이상 선택해주세요');
- return;
- }
-
- postTripSchedule({
- tourLogData: {
- name: tourInfo.name,
- locationName: tourInfo.locationName,
- startTime: tourInfo.startTime.toISOString().slice(0, -5),
- endTime: tourInfo.endTime.toISOString().slice(0, -5),
- },
- tourActivityDataList: activities.flat().map((act) => ({
- spotName: act.spotName,
- dayNumber: act.dayNumber,
- dayTime: act.dayTime,
- orderIndex: 0,
- tourSpotData: {
- contentId: act.tourSpotDto.id,
- contentTypeId: act.tourSpotDto.typeId,
- },
- })),
- }).then((res) => {
- const { data: logId } = res;
-
- setIsLoading(true);
- getTripSchedule(logId)
- .then((dto) => {
- load(dto);
- onNextPage();
- })
- .catch(() => {
- createToast('error', '다시 시도해주세요.');
- })
- .finally(() => {
- setIsLoading(false);
- });
- });
- }}
- >
- 여행 완성
-
-
- )}{' '}
- {state.ui === 'search' && (
- {
- if (!state.location) return;
-
- getTourSpots(state.location, tourInfo.sigunguCode ?? '').then(
- (res) => {
- setState((prev) => ({
- ...prev,
- ui: 'confirm',
- tourSpotDto: res,
- }));
- },
- );
- }}
- onContentChange={(value) =>
- setState((prev) => ({ ...prev, location: value }))
- }
- onPrevPage={() => {
- setState((prev) => ({ ...prev, ui: 'main' }));
+
+
+
+
+ {tourInfo.locationName}
+ {activities.map((acts, index) => (
+ {
+ onRecommendPage(index + 1);
}}
/>
- )}
- {state.ui === 'confirm' &&
- state.tourSpotDto &&
- state.day &&
- state.location && (
- {
- if (
- state.day &&
- !activities[state.day - 1]?.find(
- (activity) => activity.dayTime === time,
- )
- ) {
- setState((prev) => ({ ...prev, time }));
- } else {
- createToast('error', '이미 선택된 시간대입니다!');
- }
- }}
- onConfirm={() => {
- if (
- !state.day ||
- !state.time ||
- !state.tourSpotDto ||
- !tourInfo.startTime ||
- !tourInfo.endTime
- )
- return;
-
- addActivity(state.day, {
- dayNumber: state.day,
- dayTime: state.time,
- spotName: state.tourSpotDto.data.title,
- tourSpotDto: {
- id: state.tourSpotDto.data.contentId,
- typeId: state.tourSpotDto.data.contentTypeId,
- title: state.tourSpotDto.data.title,
- sigunguCode: state.tourSpotDto.data.sigunguCode,
- },
- orderIndex: 0,
- });
-
- setState(() => ({ ui: 'main' }));
- }}
- onPrevPage={() => {
- setState((prev) => ({ ...prev, ui: 'search' }));
- }}
- />
- )}
- >
+ ))}
+ {
+ if (!enabled) return;
+
+ if (
+ !tourInfo.name ||
+ !tourInfo.locationName ||
+ !tourInfo.startTime ||
+ !tourInfo.endTime
+ )
+ return;
+
+ if (activities.some((v) => v.length === 0)) {
+ createToast('info', '장소를 한 개 이상 선택해주세요');
+ return;
+ }
+
+ postTripSchedule({
+ tourLogData: {
+ name: tourInfo.name,
+ locationName: tourInfo.locationName,
+ startTime: tourInfo.startTime.toISOString().slice(0, -5),
+ endTime: tourInfo.endTime.toISOString().slice(0, -5),
+ },
+ tourActivityDataList: activities.flat().map((act) => ({
+ spotName: act.spotName,
+ dayNumber: act.dayNumber,
+ dayTime: act.dayTime,
+ orderIndex: 0,
+ tourSpotData: {
+ contentId: act.tourSpotDto.id,
+ contentTypeId: act.tourSpotDto.typeId,
+ },
+ })),
+ })
+ .then((res) => {
+ const { data: logId } = res;
+
+ setIsLoading(true);
+ getTripSchedule(logId)
+ .then((dto) => {
+ load(dto);
+ onNextPage();
+ })
+ .catch(() => {
+ createToast('error', '다시 시도해주세요.');
+ })
+ .finally(() => {
+ setIsLoading(false);
+ });
+ })
+ .finally(() => setEnabled(false));
+ }}
+ >
+ 여행 완성
+
+
);
}
diff --git a/src/components/travel/traveler/TravelerTravelArrange.tsx b/src/components/travel/traveler/TravelerTravelArrange.tsx
index 408e21f..a779163 100644
--- a/src/components/travel/traveler/TravelerTravelArrange.tsx
+++ b/src/components/travel/traveler/TravelerTravelArrange.tsx
@@ -9,10 +9,8 @@ import { useTripStore } from '@/features/trip/trip.slice';
export function TravelerTravelArrange({
onNextPage,
- onPrevPage,
}: {
onNextPage: () => void;
- onPrevPage: () => void;
}) {
const { tourInfo, activities, isLoading } = useTripStore();
@@ -20,13 +18,6 @@ export function TravelerTravelArrange({
return (
-
-
-
{tourInfo.locationName}
{activities.map((acts, index) => (
diff --git a/src/features/member/member.hook.ts b/src/features/member/member.hook.ts
index 3ff6f60..9261043 100644
--- a/src/features/member/member.hook.ts
+++ b/src/features/member/member.hook.ts
@@ -71,7 +71,7 @@ export const useChangePassword = () =>
oldPassword: string;
newPassword: string;
}) => putPassword(oldPassword, newPassword),
- onSuccess: (data) => data.data,
+ onSuccess: (data) => data,
});
export const useAuth = () => {
diff --git a/src/features/toast/toast.hook.ts b/src/features/toast/toast.hook.ts
index c699093..3075d42 100644
--- a/src/features/toast/toast.hook.ts
+++ b/src/features/toast/toast.hook.ts
@@ -6,9 +6,13 @@ import { type ToastType } from './toast.type';
export function useToast() {
const { addToast } = useToastStore();
- const createToast = (type: ToastType, message: string) => {
+ const createToast = (
+ type: ToastType,
+ message: string,
+ duration: number = 2000,
+ ) => {
const id = uuidv4();
- addToast({ id, type, message });
+ addToast({ id, type, message, duration });
};
return { createToast };
diff --git a/src/features/toast/toast.type.ts b/src/features/toast/toast.type.ts
index f1dfe47..7c48e1c 100644
--- a/src/features/toast/toast.type.ts
+++ b/src/features/toast/toast.type.ts
@@ -4,6 +4,7 @@ export interface Toast {
id: string;
message: string;
type: ToastType;
+ duration?: number;
}
export interface ToastState {
diff --git a/src/features/trip/trip.slice.ts b/src/features/trip/trip.slice.ts
index b4b3818..4fad5b3 100644
--- a/src/features/trip/trip.slice.ts
+++ b/src/features/trip/trip.slice.ts
@@ -34,6 +34,7 @@ export interface TripState {
tourInfo: TourInfo;
activities: Array>;
+ selectedDay?: number;
isLoading: boolean;
recommendContent?: string;
recommendedItems: TripItem[];
@@ -50,8 +51,9 @@ export interface TripAction {
removeTour: (day: number) => void;
addActivity: (day: number, activity: Activity) => void;
removeActivity: (day: number, activity: Activity) => void;
- fillActivities: (items: Activity[]) => void;
+ fillActivities: (day: number, items: Activity[]) => void;
load: (dto: GetTripScheduleResponseDTO) => void;
+ setSelectedDay: (day: number) => void;
}
const initialState: TripState = {
@@ -118,13 +120,15 @@ export const useTripStore = create((set, get) => ({
addTour: () =>
set((prev) => {
const { tourInfo, activities } = prev;
- if (!tourInfo.startTime || !tourInfo.endTime) return prev;
+
+ if (!tourInfo.startTime) return prev;
+
+ const { startTime } = tourInfo;
+ const endTime = tourInfo.endTime ?? startTime;
const DAY = 60 * 60 * 24 * 1000;
const days =
- (Math.round(tourInfo.endTime.getTime() - tourInfo.startTime.getTime()) +
- 1) /
- DAY;
+ (Math.round(endTime.getTime() - startTime.getTime()) + 1) / DAY;
return {
...prev,
@@ -132,7 +136,7 @@ export const useTripStore = create((set, get) => ({
...tourInfo,
endTime:
days >= activities.length
- ? new Date(tourInfo.endTime.getTime() + DAY)
+ ? new Date(endTime.getTime() + DAY)
: tourInfo.endTime,
},
activities: [...prev.activities, []],
@@ -180,42 +184,22 @@ export const useTripStore = create((set, get) => ({
),
})),
- fillActivities: (items) =>
+ fillActivities: (day, items) =>
set((prev) => {
const { activities } = prev;
const newActivities = [...activities];
- items.forEach((activity) => {
- const targetTime = activity.dayTime;
-
- let flag = false;
- newActivities.some((acts, index) => {
- if (acts.find((act) => act.dayTime === targetTime)) return false;
- acts.push({ ...activity, dayNumber: index + 1 });
- acts.sort((a, b) => TIME_ORDER[a.dayTime] - TIME_ORDER[b.dayTime]);
- flag = true;
- return true;
- });
-
- if (flag) return;
-
- newActivities.forEach((acts, index) => {
- if (acts.length === 4) return;
-
- const remaining = acts.reduce<
- ('MORNING' | 'AFTERNOON' | 'EVENING' | 'NIGHT')[]
- >(
- (res, curr) => res.filter((v) => v !== curr.dayTime),
- ['MORNING', 'AFTERNOON', 'EVENING', 'NIGHT'],
- );
-
- const time = remaining.shift();
- if (time) {
- acts.push({ ...activity, dayTime: time, dayNumber: index + 1 });
- acts.sort((a, b) => TIME_ORDER[a.dayTime] - TIME_ORDER[b.dayTime]);
- }
- });
+ items.forEach((activity, index) => {
+ if (
+ newActivities[day - 1].some((act) => act.dayTime === activity.dayTime)
+ )
+ return;
+
+ newActivities[day - 1].push({ ...activity, dayNumber: index + 1 });
+ newActivities[day - 1].sort(
+ (a, b) => TIME_ORDER[a.dayTime] - TIME_ORDER[b.dayTime],
+ );
});
return { ...prev, activities: newActivities };
@@ -238,4 +222,6 @@ export const useTripStore = create((set, get) => ({
return { ...prev, activities: newActivities };
}),
+
+ setSelectedDay: (day) => set((prev) => ({ ...prev, selectedDay: day })),
}));
diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx
index ba75b65..dd2f6f1 100644
--- a/src/providers/AuthProvider.tsx
+++ b/src/providers/AuthProvider.tsx
@@ -1,14 +1,14 @@
'use client';
import axios from 'axios';
-import { useRouter, usePathname } from 'next/navigation';
+import { usePathname, useRouter } from 'next/navigation';
import {
createContext,
- useLayoutEffect,
useCallback,
- useState,
- useMemo,
useEffect,
+ useLayoutEffect,
+ useMemo,
+ useState,
} from 'react';
import { useToast } from '@/features/toast';
@@ -120,7 +120,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
async (error) => {
if (
error.response?.status === 500 &&
- error.response?.data.data === '필터 내부의 예외가 발생했습니다.'
+ error.response?.data.data === '필터 내부의 예외가 발생했습니다.' &&
+ !error.response?.request.responseURL.endsWith('namechange')
) {
createToast('error', '서버와의 통신에 실패하였습니다.');
}
diff --git a/src/providers/ToastProvider.tsx b/src/providers/ToastProvider.tsx
index 3edc73c..f7b41ba 100644
--- a/src/providers/ToastProvider.tsx
+++ b/src/providers/ToastProvider.tsx
@@ -29,22 +29,25 @@ interface ToastContainer {
function ToastItem({ toast }: { toast: Toast }) {
const [visible, setVisible] = useState(true);
const { removeToast } = useToastStore();
- const { id, type, message } = toast;
+ const { id, type, message, duration } = toast;
useEffect(() => {
const timer = setTimeout(() => {
setVisible(false);
- }, DURATION);
+ }, duration ?? DURATION);
- const removeTimer = setTimeout(() => {
- removeToast(id);
- }, DURATION + ANIMATION);
+ const removeTimer = setTimeout(
+ () => {
+ removeToast(id);
+ },
+ (duration ?? DURATION) + ANIMATION,
+ );
return () => {
clearTimeout(timer);
clearTimeout(removeTimer);
};
- }, [id, removeToast]);
+ }, [id, removeToast, duration]);
return (
{message}
@@ -64,13 +67,13 @@ const fadeOut = keyframes`
const getToastColors = (type: ToastType) => {
switch (type) {
case 'success':
- return { backgroundColor: '#6B67F9' };
+ return { backgroundColor: '#f7f7fc' };
case 'error':
return { backgroundColor: '#FF6F61' };
case 'info':
- return { backgroundColor: '#505050' };
+ return { backgroundColor: '#f7f7fc' };
default:
- return { backgroundColor: '#505050' };
+ return { backgroundColor: '#f7f7fc' };
}
};
@@ -84,13 +87,12 @@ const styles = {
align-items: center;
flex-direction: column;
gap: 1rem;
- overflow: hidden;
`,
container: styled.div`
width: 90%;
border-radius: 8px;
- color: #fff;
+ color: ${({ $type }) => ($type === 'error' ? 'white' : '#505050')};
font-family: 'Noto Sans KR';
font-size: 1rem;
font-style: normal;
@@ -100,6 +102,7 @@ const styles = {
background: ${({ $type }) => getToastColors($type).backgroundColor};
text-align: center;
padding: 1rem;
+ box-shadow: 0px 1px 4px 0px #6e80913d;
animation: ${({ $visible }) => ($visible ? fadeIn : fadeOut)} 0.5s ease;
transition:
opacity 0.5s ease,