Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 글로벌 토스트 추가, 비밀번호 인풋 경고문 추가, 예외처리 글로벌 토스트로 일괄 임시 처리 #59

Merged
merged 1 commit into from
Dec 8, 2024
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
9 changes: 9 additions & 0 deletions src/_app/Providers/alert/AlertContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext } from "react";

type AlertContextType = {
alert: (message: string) => Promise<void>;
};

const AlertContext = createContext<AlertContextType | null>(null);

export default AlertContext;
17 changes: 2 additions & 15 deletions src/_app/Providers/alert/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import Alert from "@/components/Alert";
import { createContext, useContext, useState, FC, ReactNode } from "react";

type AlertContextType = {
alert: (message: string) => Promise<void>;
};

const AlertContext = createContext<AlertContextType | null>(null);
import { FC, ReactNode, useState } from "react";
import AlertContext from "./AlertContext";

export const AlertProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [alertMessage, setAlertMessage] = useState<string | null>(null);
Expand Down Expand Up @@ -35,11 +30,3 @@ export const AlertProvider: FC<{ children: ReactNode }> = ({ children }) => {
</AlertContext.Provider>
);
};

export const useAlert = () => {
const context = useContext(AlertContext);
if (!context) {
throw new Error("useAlert must be used within an AlertProvider");
}
return context;
};
7 changes: 5 additions & 2 deletions src/_app/Providers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { RouterProvider } from "react-router";
import { AlertProvider } from "./alert";
import { LoadingOverlayProvider } from "./loadingOverlay";
import { ToastProvider } from "./toast";

const queryClient = new QueryClient();

const Providers = () => (
<QueryClientProvider client={queryClient}>
<LoadingOverlayProvider>
<AlertProvider>
<RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={false} />
<ToastProvider>
<RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={false} />
</ToastProvider>
</AlertProvider>
</LoadingOverlayProvider>
</QueryClientProvider>
Expand Down
9 changes: 9 additions & 0 deletions src/_app/Providers/toast/ToastContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createContext } from "react";

type ToastContextType = {
showToast: (message: string, type?: "normal" | "error") => Promise<void>;
};

const ToastContext = createContext<ToastContextType | null>(null);

export default ToastContext;
34 changes: 34 additions & 0 deletions src/_app/Providers/toast/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Toast from "@/components/Toast";
import { FC, ReactNode, useState } from "react";
import ToastContext from "./ToastContext";

export const ToastProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [toastMessage, setToastMessage] = useState<string | null>(null);
const [resolveCallback, setResolveCallback] = useState<(() => void) | null>(
null
);
const [toastType, setToastType] = useState<"normal" | "error">("normal");

const showToast = (message: string, type: "normal" | "error" = "normal") => {
return new Promise<void>((resolve) => {
setToastMessage(message);
setToastType(type);
setResolveCallback(() => resolve);
});
};

const hideToast = () => {
if (resolveCallback) resolveCallback();
setToastMessage(null);
setResolveCallback(null);
};

return (
<ToastContext.Provider value={{ showToast }}>
{children}
{toastMessage && (
<Toast message={toastMessage} hideToast={hideToast} type={toastType} />
)}
</ToastContext.Provider>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import IconMap from "@/assets/icons/map-icon.svg?react";
import { MapBottomSheet } from "@/components/BottomSheet";
import Capsule from "@/components/Capsule";
import CapsuleNameHeader from "@/components/CapsuleNameHeader";
import CustomButtons from "@/components/CustomButtons";
Expand All @@ -9,7 +10,6 @@ import { useMemo, useState } from "react";
import { useParams } from "react-router";
import MessageDetailOverlay from "./MessageDetailOverlay";
import OpenedScreenBottom from "./OpenedScreenBottom";
import { MapBottomSheet } from "@/components/BottomSheet";

interface Props {
capsule: OpenedCapsule;
Expand Down Expand Up @@ -37,8 +37,6 @@ const OpenedScreen = ({ capsule }: Props) => {
prev === capsule.messages.length - 1 ? 0 : prev + 1
);

// TODO: 맵 바텀시트 구현

const [isOverlayOpen, setIsOverlayOpen] = useState<boolean>(false);
const openOverlay = () => setIsOverlayOpen(true);
const closeOverlay = () => setIsOverlayOpen(false);
Expand All @@ -59,14 +57,6 @@ const OpenedScreen = ({ capsule }: Props) => {

return (
<div className="relative w-full h-full">
{isMapShown && (
<MapBottomSheet
setIsShown={setIsMapShown}
coordinateX={capsule.map.x}
coordinateY={capsule.map.y}
/>
)}

<div className="w-full h-full bg-primary-paper">
<CapsuleNameHeader
capsuleName={capsule.title}
Expand Down Expand Up @@ -103,6 +93,14 @@ const OpenedScreen = ({ capsule }: Props) => {
goNext={goNext}
/>
)}

{isMapShown && (
<MapBottomSheet
setIsShown={setIsMapShown}
coordinateX={capsule.map.x}
coordinateY={capsule.map.y}
/>
)}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import CapsuleDigModal from "@/components/Modals/CapsuleDigModal";
import { useDigMutate } from "@/queries/Capsule/useCapsuleService";
import { UndiggedCapsule } from "@/types/server";
import { isUndefined } from "@/utils";
import { useEffect, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router";

import UndiggedImage from "@/assets/images/undiggedImage.png";
Expand All @@ -16,7 +16,8 @@ import UndiggedImage2 from "@/assets/images/undiggedImage2.png";
import UndiggedImage3 from "@/assets/images/undiggedImage3.png";
import UndiggedImage4 from "@/assets/images/undiggedImage4.png";
import UndiggedImage5 from "@/assets/images/undiggedImage5.png";
import { useAlert } from "@/_app/Providers/alert";
import useAlert from "@/hooks/useAlert";
import useToast from "@/hooks/useToast";

interface Props {
capsule: UndiggedCapsule;
Expand All @@ -25,7 +26,7 @@ const UnDiggedScreen = ({ capsule }: Props) => {
const { code } = useParams();
const capsuleCode = useMemo(() => (isUndefined(code) ? "" : code), [code]);
const navigate = useNavigate();

const { showToast } = useToast();
const { alert } = useAlert();
const { setGlobalLoading } = useLoadingOverlay();
const [isDigModalOpen, setIsDigModalOpen] = useState<boolean>(false);
Expand All @@ -41,25 +42,33 @@ const UnDiggedScreen = ({ capsule }: Props) => {
const digModalCallback = async () => {
if (capsule.goalTime > new Date().getTime()) {
await alert("오픈시간이 지나, 캡슐 파묻기를 실패했습니다.");

return;
}

setGlobalLoading(true);

mutateAsync({ code: capsuleCode, password: inputPassword })
.then(() => setTimeout(() => setIsDigCompleteModalOpen(true), 1000))
.catch(() => setTimeout(() => setIsDigFailModalOpen(true), 1000))
// .then(() => setTimeout(() => setIsDigCompleteModalOpen(true), 1000))
// .catch(() => setTimeout(() => setIsDigFailModalOpen(true), 1000))
.then(() => setTimeout(() => showToast("캡슐을 파묻었어요!"), 1000))
.catch(() =>
setTimeout(
() =>
showToast("파묻기에 실패했어요. 비밀번호를 확인해주세요.", "error"),
1000
)
)
.finally(() => {
hideDigModal();
setTimeout(() => setGlobalLoading(false), 1000);
});
};

const [isDigCompleteModalOpen, setIsDigCompleteModalOpen] =
useState<boolean>(false);
const [isDigFailModalOpen, setIsDigFailModalOpen] = useState<boolean>(false);
// const [isDigCompleteModalOpen, setIsDigCompleteModalOpen] =
// useState<boolean>(false);
// const [isDigFailModalOpen, setIsDigFailModalOpen] = useState<boolean>(false);
// TODO: 파묻기 시도 후 콜백 설정(에러메시지 분기처리)
useEffect(() => console.log(isDigCompleteModalOpen, isDigFailModalOpen));

const [isMapShown, setIsMapShown] = useState<boolean>(false);
const onClickOpenMap = () => setIsMapShown(true);
Expand Down Expand Up @@ -144,14 +153,6 @@ const UnDiggedScreen = ({ capsule }: Props) => {
)}

<CustomButtons.CapsuleShareFAB code={capsuleCode} />
{isDigModalOpen && (
<CapsuleDigModal
inputPassword={inputPassword}
handleInputPassword={handleInputPassword}
hideModal={hideDigModal}
onClick={digModalCallback}
/>
)}
</>
);
};
Expand Down
1 change: 0 additions & 1 deletion src/_app/pages/CapsuleDetailPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const CapsuleDetailPage = () => {
const { isPending, error, data } = useCapsuleQuery({ code: capsuleCode });
const { setGlobalLoading } = useLoadingOverlay();

useEffect(() => console.log("data", data), [data]);
useEffect(() => {
if (error) navigate("/");
}, [error]);
Expand Down
8 changes: 7 additions & 1 deletion src/_app/pages/CapsuleSharePage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import IconCopy from "@/assets/icons/copy-icon.svg?react";
import BackButtonHeader from "@/components/BackButtonHeader";
import { domain } from "@/constants/environments";
import useToast from "@/hooks/useToast";
import { isUndefined } from "@/utils";
import { QRCodeSVG } from "qrcode.react";
import { useMemo } from "react";
Expand All @@ -14,9 +15,14 @@ const CapsuleSharePage = () => {
[capsuleCode]
);

const { showToast } = useToast();

const navigate = useNavigate();
const goBack = () => navigate(`/capsule/${encodeURIComponent(capsuleCode)}`);
const onClickCopy = () => navigator.clipboard.writeText(shareUrl);
const onClickCopy = () =>
navigator.clipboard
.writeText(shareUrl)
.then(() => showToast("클립보드 복사 완료!"));

return (
<div className="w-full h-full bg-primary-paper">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ const PasswordInputStep = ({ password, setPassword }: Props) => {
placeholder="캡슐비밀번호를 입력해 주세요."
mountFocus
/>
<div className="h-[16px]" />
<div className="w-full py-[22px] px-[18px] rounded-[16px] bg-[#F8F8F8] text-[14px] text-[#A1A1A1] flex flex-col">
<p>* 캡슐 파묻기 시에 사용될 비밀번호입니다.</p>
<p>
* 비밀번호 분실 시 재설정이{" "}
<span className="font-bold text-error">불가능</span>합니다.
</p>
</div>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useLoadingOverlay } from "@/_app/Providers/loadingOverlay";
import IconPlus from "@/assets/icons/plus-icon.svg?react";
import StepHeader from "@/components/Funnel/StepHeader";
import useToast from "@/hooks/useToast";
import { Photo } from "@/types/client";
import { isNill, isUndefined } from "@/utils";
import clsx from "clsx";
Expand All @@ -14,6 +15,7 @@ interface Props {
const PhotoInputStep = ({ photo, setPhoto }: Props) => {
const { setGlobalLoading } = useLoadingOverlay();
const inputRef = useRef<HTMLInputElement>(null);
const { showToast } = useToast();
const onClickUpload = () => {
if (isNill(inputRef.current)) {
return;
Expand All @@ -23,6 +25,8 @@ const PhotoInputStep = ({ photo, setPhoto }: Props) => {
};
const onChange = async (event: ChangeEvent<HTMLInputElement>) => {
if (isNill(event.target) || isNill(event.target.files)) {
showToast("이미지를 불러오는 데 실패했어요.", "error");

return;
}

Expand All @@ -32,6 +36,7 @@ const PhotoInputStep = ({ photo, setPhoto }: Props) => {
const maxSize = 5 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
setGlobalLoading(false);
showToast("이미지 용량이 너무 커요.", "error");

return;
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions src/components/Accordion/DatePicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { DayPicker } from "react-day-picker";
import { ko } from "date-fns/locale";
import { DayPicker } from "react-day-picker";

import "react-day-picker/style.css";
import "./index.css";
import { useAccordion } from "@/components/Accordion/BaseAccordion/useAcordionContext";
import { Dispatch, SetStateAction } from "react";
import "react-day-picker/style.css";
import "./index.css";

interface Props {
dataValue: Date;
Expand All @@ -13,7 +13,7 @@ interface Props {

/**데이트 선택 컴포넌트 */
export default function DatePicker({ dataValue, setDataValue }: Props) {
//TODO : 좋은 방법
// TODO: 좋은 방법
const { setActiveSection } = useAccordion();

const formatYearMonthKorean = (month: Date) => {
Expand All @@ -27,7 +27,7 @@ export default function DatePicker({ dataValue, setDataValue }: Props) {
setActiveSection("time");
};

//이전 날짜 차단
// 이전 날짜 차단
const disabledDays = {
before: new Date(),
};
Expand Down
9 changes: 6 additions & 3 deletions src/components/Modals/CapsuleDigModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import IconErrorBlue from "@/assets/icons/error-icon-blue.svg?react";
import IconError from "@/assets/icons/error-icon.svg?react";
import CustomInput from "@/components/CustomInput";
import TwoButtonModal from "../ModalBases/TwoButtonModal";

Expand Down Expand Up @@ -29,15 +29,18 @@ const CapsuleDigModal = ({
</p>
<div className="w-[10px]" />
<CustomInput
mountFocus
value={inputPassword}
setValue={handleInputPassword}
size="small"
placeholder="비밀번호 입력"
/>
<div className="h-[10px]" />
<div className="flex gap-[5px] items-center">
<IconErrorBlue />
<p className="text-[14px] text-primary-main">묻으면 빠꾸 X</p>
<IconError />
<p className="text-[14px] text-error text-center">
묻으면 새로운 내용을 추가할 수 없어요.
</p>
</div>
</div>
</TwoButtonModal>
Expand Down
8 changes: 5 additions & 3 deletions src/components/Modals/MessageCreateCompleteModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import IconComplete from "@/assets/icons/complete-icon.svg?react";
import imageSrc from "@/assets/images/capsule-create-modal-illust.png";
import OneButtonModal from "../ModalBases/OneButtonModal";

interface Props {
Expand All @@ -9,9 +9,11 @@ interface Props {
const MessageCreateCompleteModal = ({ hideModal, onClick }: Props) => (
<OneButtonModal button={{ title: "확인", onClick }} hideModal={hideModal}>
<div className="flex flex-col items-center gap-[10px]">
<IconComplete />
<p className="text-[18px] font-bold">캡슐이 추가되었습니다!</p>
<p className="text-[#9A9A9A] text-center text-[14px]">야호!</p>
<p className="text-[#9A9A9A] text-center text-[14px]">
소중한 추억이 쌓이고 있어요!
</p>
<img src={imageSrc} className="w-[158px]" />
</div>
</OneButtonModal>
);
Expand Down
Loading
Loading