From 0a5b1380e07926613f1997772bb942f2fd589dfa Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Thu, 24 Aug 2023 18:02:10 +0900 Subject: [PATCH 1/5] Refactor: ModalMypageModify --- .../Chat/MessagesBody/MessageSet/index.tsx | 4 +- src/components/Input/index.tsx | 26 +++ src/components/ModalPopup/ModalCallTaxi.tsx | 22 +-- .../ModalPopup/ModalChatSettlement.tsx | 84 +++++---- ...{ModalModify.jsx => ModalMypageModify.tsx} | 162 ++++++++---------- .../ModalPopup/ModalReportInChatting.tsx | 4 +- src/components/ModalPopup/index.tsx | 2 +- .../User/{ProfileImg.jsx => ProfileImage.tsx} | 31 ++-- src/components/User/index.tsx | 4 +- src/pages/Mypage/index.tsx | 10 +- 10 files changed, 181 insertions(+), 168 deletions(-) create mode 100644 src/components/Input/index.tsx rename src/components/ModalPopup/{ModalModify.jsx => ModalMypageModify.tsx} (68%) rename src/components/User/{ProfileImg.jsx => ProfileImage.tsx} (59%) diff --git a/src/components/Chat/MessagesBody/MessageSet/index.tsx b/src/components/Chat/MessagesBody/MessageSet/index.tsx index 8b298017f..f3a8104a8 100644 --- a/src/components/Chat/MessagesBody/MessageSet/index.tsx +++ b/src/components/Chat/MessagesBody/MessageSet/index.tsx @@ -4,7 +4,7 @@ import type { BotChat, LayoutType, UserChat } from "types/chat"; import { useValueRecoilState } from "hooks/useFetchRecoilState"; -import ProfileImg from "components/User/ProfileImg"; +import ProfileImage from "components/User/ProfileImage"; import MessageAccount from "./MessageAccount"; import MessageImage from "./MessageImage"; @@ -139,7 +139,7 @@ const MessageSet = ({ chats, layoutType }: MessageSetProps) => { {authorId === "bot" ? ( ) : ( - + )} )} diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx new file mode 100644 index 000000000..a2c4e985a --- /dev/null +++ b/src/components/Input/index.tsx @@ -0,0 +1,26 @@ +import theme from "tools/theme"; + +type InputProps = { + value?: string; + onChangeValue?: (v: string) => void; + className?: string; // for emotion +}; + +const Input = ({ value, onChangeValue, className }: InputProps) => ( + onChangeValue?.(e.target.value)} + className={className} + css={{ + border: "none", + outline: "none", + borderRadius: "6px", + padding: "6px 12px", + background: theme.purple_light, + boxShadow: theme.shadow_purple_input_inset, + ...theme.font14, + }} + /> +); + +export default Input; diff --git a/src/components/ModalPopup/ModalCallTaxi.tsx b/src/components/ModalPopup/ModalCallTaxi.tsx index 8ba16384f..97ed443be 100644 --- a/src/components/ModalPopup/ModalCallTaxi.tsx +++ b/src/components/ModalPopup/ModalCallTaxi.tsx @@ -1,23 +1,17 @@ import Modal from "components/Modal"; import BodyCallTaxi from "./Body/BodyCallTaxi"; -import { BodyRoomShareProps } from "./Body/BodyRoomShare"; import theme from "tools/theme"; import LocalTaxiRoundedIcon from "@mui/icons-material/LocalTaxiRounded"; -type ModalCallTaxiProps = { - isOpen: boolean; - onChangeIsOpen?: (isOpen: boolean) => void; - roomInfo: BodyRoomShareProps["roomInfo"]; -}; +type ModalCallTaxiProps = Omit< + Parameters[0], + "padding" | "children" +> & { roomInfo: Room }; -const ModalCallTaxi = ({ - isOpen, - onChangeIsOpen, - roomInfo, -}: ModalCallTaxiProps) => { +const ModalCallTaxi = ({ roomInfo, ...modalProps }: ModalCallTaxiProps) => { const styleTitle = { ...theme.font18, display: "flex", @@ -30,11 +24,7 @@ const ModalCallTaxi = ({ }; return ( - +
택시 호출하기 diff --git a/src/components/ModalPopup/ModalChatSettlement.tsx b/src/components/ModalPopup/ModalChatSettlement.tsx index e33bfd87e..582a156ff 100644 --- a/src/components/ModalPopup/ModalChatSettlement.tsx +++ b/src/components/ModalPopup/ModalChatSettlement.tsx @@ -3,6 +3,7 @@ import { useCallback } from "react"; import { useAxios } from "hooks/useTaxiAPI"; import Button from "components/Button"; +import DottedLine from "components/DottedLine"; import Modal from "components/Modal"; import alertAtom from "atoms/alert"; @@ -10,6 +11,8 @@ import { useSetRecoilState } from "recoil"; import theme from "tools/theme"; +import AccountBalanceWalletRoundedIcon from "@mui/icons-material/AccountBalanceWalletRounded"; + type ModalChatSettlementProps = Omit< Parameters[0], "padding" | "children" | "onEnter" @@ -38,45 +41,60 @@ const ModalChatSettlement = ({ [roomId, modalProps.onChangeIsOpen, onRecall] ); - const styleTextCont = { - textAlign: "center" as any, - }; - const styleTextCont2 = { - textAlign: "center" as any, - lineHieght: "12px", - paddingTop: "6px", - fontSize: "10px", - color: "888888", + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", }; - const styleTxt1 = { - fontSize: "16px", - fontWeight: "bold", + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", }; - const styleTxt2 = { - fontSize: "16px", - fontWeight: 300, - }; - const styleTxt3 = { - fontSize: "16px", - fontWeight: "bold", - color: "#6E3678", + const styleText = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px 12px", }; + // const styleTextCont = { + // textAlign: "center" as any, + // }; + // const styleTextCont2 = { + // textAlign: "center" as any, + // lineHieght: "12px", + // paddingTop: "6px", + // fontSize: "10px", + // color: "888888", + // }; + // const styleTxt1 = { + // fontSize: "16px", + // fontWeight: "bold", + // }; + // const styleTxt2 = { + // fontSize: "16px", + // fontWeight: 300, + // }; + // const styleTxt3 = { + // fontSize: "16px", + // fontWeight: "bold", + // color: "#6E3678", + // }; return ( - -
-
- 결제 - - 완료 - 하시겠습니까? -
-
- 꼭 택시비를 결제한 본인만 완료해주세요. -
- 완료 후 취소는 불가능합니다. -
+ +
+ + 정산하기 +
+
동승자들에게 송금을 요청할 수 있습니다.
+
+ • 완료 후 취소는 불가능합니다. +
+ + • 본인이 택시 요금을 계산한 경우에만 진행해주세요. +
+
{ - const style = { - margin: "auto", - width: "110px", - height: "110px", - borderRadius: "55%", - overflow: "hidden", - }; - return ( -
- {props.profileImgUrl && ( - - )} -
- ); -}; -ProfImg.propTypes = { - profileImgUrl: PropTypes.string, - token: PropTypes.any, +type ModalMypageModifyProps = Omit< + Parameters[0], + "padding" | "children" | "onEnter" +> & { profToken?: string; onUpdate?: () => void }; + +type ProfileImageLargeProps = Parameters[0]; + +type ButtonProfileImageProps = { + onUpdate?: ModalMypageModifyProps["onUpdate"]; }; -const BtnProfImg = (props) => { +const ProfileImageLarge = (props: ProfileImageLargeProps) => ( +
+ +
+); + +const ButtonProfileImage = ({ onUpdate }: ButtonProfileImageProps) => { const { t } = useTranslation("mypage"); const axios = useAxios(); - const inputImage = useRef(null); - const [profileAlert, setProfileAlert] = useState(null); + const inputRef = useRef(null); + const [profileAlert, setProfileAlert] = useState>(null); const fetchLoginInfo = useFetchRecoilState("loginInfo"); useEffect(() => { @@ -57,10 +61,10 @@ const BtnProfImg = (props) => { return () => clearTimeout(timeoutID); }, [profileAlert]); - const handleUploadProfileImage = async () => { + const handleUploadProfileImage = useCallback(async () => { setProfileAlert("LOADING"); try { - const image = await convertImage(inputImage.current?.files?.[0]); + const image = await convertImage(inputRef.current?.files?.[0]); if (!image) return; const data = await axios({ url: "/users/editProfileImg/getPUrl", @@ -83,7 +87,7 @@ const BtnProfImg = (props) => { }); if (data2?.result) { fetchLoginInfo(); - props.onUpdate(); + onUpdate?.(); setProfileAlert("SUCCESS"); return; } @@ -93,7 +97,12 @@ const BtnProfImg = (props) => { } catch (e) { setProfileAlert("FAIL"); } - }; + }, [onUpdate]); + + const onClick = useCallback(() => { + if (!profileAlert) inputRef.current?.click(); + }, [profileAlert]); + const style = { textAlign: "center", ...theme.font10_bold, @@ -108,21 +117,16 @@ const BtnProfImg = (props) => { width: "fit-content", margin: "16px auto", cursor: profileAlert ? "default" : "pointer", - }; - const onClick = () => { - if (!profileAlert) { - inputImage.current.click(); - } - }; + } as const; return ( -
+
{profileAlert === "SUCCESS" ? t("page_modify.profile_image_success") @@ -134,12 +138,14 @@ const BtnProfImg = (props) => {
); }; -BtnProfImg.propTypes = { - onUpdate: PropTypes.func, - onClose: PropTypes.func, -}; -const ModalModify = (props) => { +const ModalMypageModify = ({ + profToken, + onUpdate, + isOpen, + onChangeIsOpen, + ...modalProps +}: ModalMypageModifyProps) => { const { t } = useTranslation("mypage"); const axios = useAxios(); @@ -151,11 +157,11 @@ const ModalModify = (props) => { const setAlert = useSetRecoilState(alertAtom); useEffect(() => { - if (props.isOpen) { + if (isOpen) { setNickname(loginInfo?.nickname || ""); setAccount(loginInfo?.account || ""); } - }, [loginInfo, props.isOpen]); + }, [loginInfo, isOpen]); const isEditable = regExpTest.account(account) && regExpTest.nickname(nickname); @@ -184,36 +190,25 @@ const ModalModify = (props) => { if (isNeedToUpdateLoginInfo) { fetchLoginInfo(); } - props.onChangeIsOpen(false); + onChangeIsOpen?.(false); }; const styleName = { ...theme.font20, textAlign: "center", marginBottom: "16px", - }; + } as const; const styleTitle = { display: "flex", alignItems: "center", - ...theme.font14, color: theme.gray_text, whiteSpace: "nowrap", - }; + ...theme.font14, + } as const; const styleContent = { ...theme.font14, marginLeft: "12px", }; - const styleNickname = { - width: "100%", - ...theme.font14, - border: "none", - outline: "none", - borderRadius: "6px", - padding: "6px 12px", - marginLeft: "10px", - background: theme.purple_light, - boxShadow: theme.shadow_purple_input_inset, - }; const styleButton = { display: "flex", justifyContent: "space-between", @@ -222,36 +217,33 @@ const ModalModify = (props) => { return ( -
{loginInfo?.name}
- - props.onChangeIsOpen(false)} - onUpdate={props.onUpdate} - /> +
{loginInfo?.name}
+ {loginInfo?.profileImgUrl && ( + + )} + -
-
+
+
{t("student_id")} -
{loginInfo?.subinfo?.kaist}
+
{loginInfo?.subinfo?.kaist}
-
+
{t("email")} -
{loginInfo?.email}
+
{loginInfo?.email}
-
+
{t("nickname")} - setNickname(e.target.value)} + onChangeValue={setNickname} + css={{ width: "100%", marginLeft: "10px" }} />
{ setAccountNumber={setAccount} />
-
+
@@ -288,11 +280,5 @@ const ModalModify = (props) => { ); }; -ModalModify.propTypes = { - profToken: PropTypes.any, - isOpen: PropTypes.bool, - onChangeIsOpen: PropTypes.func, - onUpdate: PropTypes.func, -}; -export default ModalModify; +export default ModalMypageModify; diff --git a/src/components/ModalPopup/ModalReportInChatting.tsx b/src/components/ModalPopup/ModalReportInChatting.tsx index b400b7ca1..9601d37fa 100644 --- a/src/components/ModalPopup/ModalReportInChatting.tsx +++ b/src/components/ModalPopup/ModalReportInChatting.tsx @@ -6,7 +6,7 @@ import { useAxios } from "hooks/useTaxiAPI"; import Button from "components/Button"; import Modal from "components/Modal"; -import ProfileImg from "components/User/ProfileImg"; +import ProfileImage from "components/User/ProfileImage"; import alertAtom from "atoms/alert"; import { useSetRecoilState } from "recoil"; @@ -172,7 +172,7 @@ const ModalReportInChatting = ({
- +
{name}
diff --git a/src/components/ModalPopup/index.tsx b/src/components/ModalPopup/index.tsx index 9869fce77..1dfc0fc34 100644 --- a/src/components/ModalPopup/index.tsx +++ b/src/components/ModalPopup/index.tsx @@ -2,7 +2,7 @@ export { default as ModalChatCancel } from "./ModalChatCancel"; export { default as ModalChatPayement } from "./ModalChatPayment"; export { default as ModalChatSettlement } from "./ModalChatSettlement"; export { default as ModalCredit } from "./ModalCredit"; -export { default as ModalModify } from "./ModalModify"; +export { default as ModalMypageModify } from "./ModalMypageModify"; export { default as ModalNotification } from "./ModalNotification"; export { default as ModalPrivacyPolicy } from "./ModalPrivacyPolicy"; export { default as ModalReport } from "./ModalReport"; diff --git a/src/components/User/ProfileImg.jsx b/src/components/User/ProfileImage.tsx similarity index 59% rename from src/components/User/ProfileImg.jsx rename to src/components/User/ProfileImage.tsx index 1fb3f6fb7..58bb297cd 100644 --- a/src/components/User/ProfileImg.jsx +++ b/src/components/User/ProfileImage.tsx @@ -1,4 +1,3 @@ -import PropTypes from "prop-types"; import { useEffect, useState } from "react"; import theme from "tools/theme"; @@ -6,18 +5,20 @@ import { getS3Url } from "tools/trans"; import defaultImg from "static/assets/profileImgOnError.png"; -const ProfileImg = (props) => { - const getSrc = () => - getS3Url(`/profile-img/${props.path}?token=${props.token}`); +type ProfileImageProps = { + url: string; + token?: string; +}; + +const ProfileImage = ({ url, token }: ProfileImageProps) => { + const getSrc = () => getS3Url(`/profile-img/${url}?token=${token || ""}`); const [src, setSrc] = useState(getSrc()); - useEffect(() => { - setSrc(getSrc()); - }, [props.path, props.token]); + useEffect(() => setSrc(getSrc()), [url, token]); return (
{ > { height: "100%", objectFit: "cover", }} - alt={`/profile-img/${props.path}`} + alt={`/profile-img/${url}`} onError={() => setSrc(defaultImg)} />
); }; -ProfileImg.propTypes = { - path: PropTypes.string, - token: PropTypes.string, -}; -ProfileImg.defaultProps = { - token: "", -}; - -export default ProfileImg; +export default ProfileImage; diff --git a/src/components/User/index.tsx b/src/components/User/index.tsx index c18dc77d3..51b672f2b 100644 --- a/src/components/User/index.tsx +++ b/src/components/User/index.tsx @@ -1,4 +1,4 @@ -import ProfileImg from "./ProfileImg"; +import ProfileImage from "./ProfileImage"; import theme from "tools/theme"; @@ -25,7 +25,7 @@ const User = ({ value }: UserProps) => ( background: theme.gray_line, }} > - +
{
{loginInfo?.profileImgUrl && ( - )} @@ -159,7 +159,7 @@ const Mypage = () => {
- Date: Fri, 1 Sep 2023 02:44:21 +0900 Subject: [PATCH 2/5] Refactor: ModalChatSettlement / Payment --- src/components/AccountSelector.tsx | 90 -------- src/components/Chat/Header/SideMenu.tsx | 17 +- .../Chat/MessageForm/Popup/PopupAccount.tsx | 116 ----------- .../Chat/MessageForm/ToolSheet/index.tsx | 42 +++- src/components/Chat/MessageForm/index.tsx | 7 +- .../MessageSet/MessageAccount.tsx | 38 +++- .../Chat/MessagesBody/MessageSet/index.tsx | 13 +- src/components/Chat/MessagesBody/index.tsx | 5 +- src/components/Chat/index.tsx | 2 + src/components/Input/InputAccount.tsx | 76 +++++++ src/components/Input/Select.tsx | 62 ++++++ src/components/Input/index.tsx | 14 +- src/components/Link/LinkCopy.tsx | 2 +- src/components/Link/LinkPayment.tsx | 79 +++++++ .../ModalPopup/Body/BodyCallTaxi.tsx | 2 +- .../ModalPopup/ModalChatPayment.tsx | 193 +++++++++++++----- ...tInChatting.tsx => ModalChatReporUser.tsx} | 8 +- .../ModalPopup/ModalChatSaveAccount.tsx | 125 ++++++++++++ .../ModalPopup/ModalChatSettlement.tsx | 132 +++++++----- .../ModalPopup/ModalMypageModify.tsx | 14 +- src/components/Room/index.jsx | 8 +- src/components/Skeleton/AlertProvider.tsx | 1 + src/components/User/index.tsx | 73 ++++--- src/hooks/chat/useAccountFromChats.tsx | 17 ++ src/hooks/chat/useChatsForBody.tsx | 5 +- src/pages/Mypage/langs/ko.json | 2 +- src/pages/Search/SideResult.jsx | 46 ++--- src/static/assets/KakaoPayLogo.svg | 25 +++ .../{KakaotTaxiLogo.svg => KakaoTaxiLogo.svg} | 0 src/static/assets/TossLogo.svg | 21 ++ src/tools/chat/chats.ts | 1 - src/tools/regExpTest.js | 2 +- src/types/chat.d.ts | 1 - src/types/global.d.ts | 11 +- 34 files changed, 811 insertions(+), 439 deletions(-) delete mode 100644 src/components/AccountSelector.tsx delete mode 100644 src/components/Chat/MessageForm/Popup/PopupAccount.tsx create mode 100644 src/components/Input/InputAccount.tsx create mode 100644 src/components/Input/Select.tsx create mode 100644 src/components/Link/LinkPayment.tsx rename src/components/ModalPopup/{ModalReportInChatting.tsx => ModalChatReporUser.tsx} (97%) create mode 100644 src/components/ModalPopup/ModalChatSaveAccount.tsx create mode 100644 src/hooks/chat/useAccountFromChats.tsx create mode 100644 src/static/assets/KakaoPayLogo.svg rename src/static/assets/{KakaotTaxiLogo.svg => KakaoTaxiLogo.svg} (100%) create mode 100644 src/static/assets/TossLogo.svg diff --git a/src/components/AccountSelector.tsx b/src/components/AccountSelector.tsx deleted file mode 100644 index 2ee792101..000000000 --- a/src/components/AccountSelector.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import theme from "tools/theme"; - -import bankNames from "static/bankNames"; - -type AccountSelectorProps = { - accountNumber: string; - setAccountNumber: (account: string) => void; -}; - -const AccountSelector = (props: AccountSelectorProps) => { - const initializeAccountState = () => { - const account = props.accountNumber.split(" "); - return [account?.[0] || bankNames[0], account?.[1] || ""]; - }; - - const { t } = useTranslation("mypage"); - const [bankName, setBankName] = useState(initializeAccountState()[0]); - const [bankNumber, setBankNumber] = useState(initializeAccountState()[1]); - - useEffect(() => { - props.setAccountNumber(bankName + " " + bankNumber); - }, [bankName, bankNumber]); - - useEffect(() => { - setBankName(initializeAccountState()[0]); - setBankNumber(initializeAccountState()[1]); - }, [props.accountNumber]); - - const styleTitle: CSS = { - display: "flex", - alignItems: "center", - ...theme.font14, - color: theme.gray_text, - whiteSpace: "nowrap", - marginTop: "10px", - }; - const styleNickname: CSS = { - width: "100%", - ...theme.font14, - border: "none", - outline: "none", - borderRadius: "6px", - padding: "6px 12px", - marginLeft: "10px", - background: theme.purple_light, - boxShadow: theme.shadow_purple_input_inset, - }; - const styleBanks: CSS = { - width: "75px", - ...theme.font14, - color: theme.purple, - fontWeight: "400", - border: "none", - outline: "none", - borderRadius: "6px", - padding: "6px 4px", - marginLeft: "10px", - background: theme.purple_light, - boxShadow: theme.shadow_purple_input_inset, - textAlign: "center", - }; - return ( -
- {t("account")} - - setBankNumber(e.target.value)} - /> -
- ); -}; - -export default AccountSelector; diff --git a/src/components/Chat/Header/SideMenu.tsx b/src/components/Chat/Header/SideMenu.tsx index 84edf30bd..ccd46c4fd 100644 --- a/src/components/Chat/Header/SideMenu.tsx +++ b/src/components/Chat/Header/SideMenu.tsx @@ -76,15 +76,15 @@ const SideMenu = ({ roomInfo, isOpen, setIsOpen }: SideMenuProps) => { const [isOpenShare, setIsOpenShare] = useState(false); const [isOpenCallTaxi, setIsOpenCallTaxi] = useState(false); const [isOpenCancel, setIsOpenCancel] = useState(false); - const isDepart = useIsTimeOver(dayServerToClient(roomInfo.time)); // 방 출발 여부 + const isDeparted = useIsTimeOver(dayServerToClient(roomInfo.time)); // 방 출발 여부 const onClikcShare = useCallback(() => setIsOpenShare(true), []); const onClickCancel = useCallback( () => - isDepart + isDeparted ? setAlert("출발 시각이 이전인 방은 탑승 취소를 할 수 없습니다.") : setIsOpenCancel(true), - [isDepart] + [isDeparted] ); const onClickCallTaxi = useCallback(() => setIsOpenCallTaxi(true), []); @@ -181,9 +181,8 @@ const SideMenu = ({ roomInfo, isOpen, setIsOpen }: SideMenuProps) => {
- {/* @fixme @todo 유저의 정산 정보 넘겨주나? */} {roomInfo.part.map((item) => ( - + ))}
@@ -199,15 +198,15 @@ const SideMenu = ({ roomInfo, isOpen, setIsOpen }: SideMenuProps) => {
탑승 취소 diff --git a/src/components/Chat/MessageForm/Popup/PopupAccount.tsx b/src/components/Chat/MessageForm/Popup/PopupAccount.tsx deleted file mode 100644 index 7265991dd..000000000 --- a/src/components/Chat/MessageForm/Popup/PopupAccount.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { useEffect, useState } from "react"; -import { Link } from "react-router-dom"; - -import AccountSelector from "components/AccountSelector"; -import Button from "components/Button"; -import DottedLine from "components/DottedLine"; -import Modal from "components/Modal"; - -import loginInfoAtom from "atoms/loginInfo"; -import { useRecoilValue } from "recoil"; - -import regExpTest from "tools/regExpTest"; -import theme from "tools/theme"; - -import WalletIcon from "@mui/icons-material/Wallet"; - -type SendAccoundModalProps = { - popup: boolean; - onClickClose: () => void; - onClickOk: (account: string) => void; -}; - -const PopupAccount = (props: SendAccoundModalProps) => { - const loginInfo = useRecoilValue(loginInfoAtom); - const [accountNumber, setAccountNumber] = useState(loginInfo?.account || ""); - - const styleTitle = { - ...theme.font18, - display: "flex", - alignItems: "center", - }; - const styleIcon = { - fontSize: "21px", - margin: "0 4px 0 0", - }; - const styleText = { - ...theme.font14, - color: theme.gray_text, - }; - - const handleClickOk = () => { - if (regExpTest.account(accountNumber)) { - props.onClickOk(accountNumber); - } - }; - - useEffect(() => { - if (!props.popup) { - setAccountNumber(loginInfo?.account || ""); - } - }, [props.popup, loginInfo?.account]); - - return ( - -
-
- - 계좌 보내기 -
-
- 계좌를 변경하고 싶으신 경우 마이 페이지의 - “수정하기” 메뉴를 이용해주세요. -
- - -
-
- - -
-
- ); -}; - -export default PopupAccount; diff --git a/src/components/Chat/MessageForm/ToolSheet/index.tsx b/src/components/Chat/MessageForm/ToolSheet/index.tsx index 73d95f03d..12123a976 100644 --- a/src/components/Chat/MessageForm/ToolSheet/index.tsx +++ b/src/components/Chat/MessageForm/ToolSheet/index.tsx @@ -7,11 +7,13 @@ import { useState, } from "react"; +import useAccountFromChats from "hooks/chat/useAccountFromChats"; import { useValueRecoilState } from "hooks/useFetchRecoilState"; import useIsTimeOver from "hooks/useIsTimeOver"; import AdaptiveDiv from "components/AdaptiveDiv"; import { ModalChatPayement, ModalChatSettlement } from "components/ModalPopup"; +import ModalChatSaveAcount from "components/ModalPopup/ModalChatSaveAccount"; import ToolButton from "./ToolButton"; @@ -26,6 +28,7 @@ type ToolSheetProps = { isOpen: boolean; onChangeIsOpen?: (x: boolean) => void; onChangeUploadedImage?: (x: Nullable) => void; + account: ReturnType; }; const ToolSheet = ({ @@ -33,11 +36,14 @@ const ToolSheet = ({ isOpen, onChangeIsOpen, onChangeUploadedImage, + account, }: ToolSheetProps) => { const setAlert = useSetRecoilState(alertAtom); const { oid: userOid } = useValueRecoilState("loginInfo") || {}; + const [accountToSave, setAccountToSave] = useState(""); const [isOpenSettlement, setIsOpenSettlement] = useState(false); const [isOpenPayment, setIsOpenPayment] = useState(false); + const [isOpenSaveAccount, setIsOpenSaveAccount] = useState(true); const isDepart = useIsTimeOver( roomInfo ? dayServerToClient(roomInfo.time) : dayNowClient() ); // 방 출발 여부 @@ -60,28 +66,35 @@ const ToolSheet = ({ ); const onClickImage = useCallback(() => inputImageRef.current?.click(), []); const onClickSettlement = useCallback(() => { - if (!isDepart) setAlert("출발 시각 이후에 정산하기 요청을 보내주세요."); + if (!isDepart) + setAlert("출발 시각 이후부터 정산 및 송금하기가 가능합니다."); else if (settlementStatusForMe === "paid") - setAlert("정산하기 요청은 중복하여 보낼 수 없습니다."); + setAlert("정산하기는 중복하여 수행될 수 없습니다."); else if (roomInfo?.settlementTotal) setAlert( - "정산하기 요청을 한 사용자가 이미 있습니다." + - "만약 결제하지 않은 사용자가 정산하기 요청을 보냈다면 신고해주세요." + <> + 정산하기 요청을 한 사용자가 이미 있습니다. +
+ 만약 결제하지 않은 사용자가 정산하기 요청을 보냈다면 신고해주세요. + ); else setIsOpenSettlement(true); }, [isDepart, settlementStatusForMe]); const onClickPayment = useCallback(() => { - if (!isDepart) setAlert("출발 시각 이후에 송금하기 요청을 보내주세요."); - else if (settlementStatusForMe === "sent") - setAlert("송금하기 요청은 중복하여 보낼 수 없습니다."); + if (!isDepart) + setAlert("출발 시각 이후부터 정산 및 송금하기가 가능합니다."); else if (!roomInfo?.settlementTotal) - setAlert("정산하기 요청을 보낸 사용자가 없어 송금하기가 불가능합니다."); + setAlert("정산하기를 요청한 사용자가 없어 송금하기가 불가능합니다."); else setIsOpenPayment(true); }, [isDepart, settlementStatusForMe, setIsOpenPayment]); const onRecallSettlePayment = useCallback( () => onChangeIsOpen?.(false), [onChangeIsOpen] ); + const openSaveAccountModal = useCallback((account: string) => { + setAccountToSave(account); + setIsOpenSaveAccount(true); + }, []); const styleWrap = { position: "absolute" as any, @@ -131,15 +144,24 @@ const ToolSheet = ({ + {accountToSave && ( + + )} )}
diff --git a/src/components/Chat/MessageForm/index.tsx b/src/components/Chat/MessageForm/index.tsx index 1820ab759..d63cc96d7 100644 --- a/src/components/Chat/MessageForm/index.tsx +++ b/src/components/Chat/MessageForm/index.tsx @@ -1,7 +1,8 @@ import { RefObject, memo, useState } from "react"; -import type { LayoutType } from "types/chat"; +import type { Chats, LayoutType } from "types/chat"; +import useAccountFromChats from "hooks/chat/useAccountFromChats"; import useSendMessage from "hooks/chat/useSendMessage"; import InputText from "./InputText"; @@ -19,6 +20,7 @@ import theme from "tools/theme"; type MessageFormProps = { layoutType: LayoutType; roomInfo: Nullable; + chats: Chats; isDisplayNewMessage: boolean; isOpenToolSheet: boolean; onChangeIsOpenToolSheet: (x: boolean) => void; @@ -29,6 +31,7 @@ type MessageFormProps = { const MessageForm = ({ layoutType, roomInfo, + chats, isDisplayNewMessage, isOpenToolSheet, onChangeIsOpenToolSheet, @@ -37,6 +40,7 @@ const MessageForm = ({ }: MessageFormProps) => { const isVKDetected = useRecoilValue(isVirtualKeyboardDetectedAtom); const [uploadedImage, setUploadedImage] = useState>(null); // 업로드된 이미지 파일 + const account = useAccountFromChats(chats); const onClickNewMessage = () => { if (!messageBodyRef.current) return; @@ -74,6 +78,7 @@ const MessageForm = ({ isOpen={isOpenToolSheet} onChangeIsOpen={onChangeIsOpenToolSheet} onChangeUploadedImage={setUploadedImage} + account={account} />
diff --git a/src/components/Chat/MessagesBody/MessageSet/MessageAccount.tsx b/src/components/Chat/MessagesBody/MessageSet/MessageAccount.tsx index 28aa77e6d..257fa7184 100644 --- a/src/components/Chat/MessagesBody/MessageSet/MessageAccount.tsx +++ b/src/components/Chat/MessagesBody/MessageSet/MessageAccount.tsx @@ -1,6 +1,8 @@ -import { useMemo } from "react"; +import { useMemo, useState } from "react"; -import LinkCopy from "components/Link/LinkCopy"; +import { useValueRecoilState } from "hooks/useFetchRecoilState"; + +import { ModalChatPayement } from "components/ModalPopup"; import Button from "./Button"; @@ -9,14 +11,23 @@ import theme from "tools/theme"; import WalletRoundedIcon from "@mui/icons-material/WalletRounded"; type MessageAccountProps = { + roomInfo: Room; account: string; }; -const MessageAccount = ({ account }: MessageAccountProps) => { +const MessageAccount = ({ roomInfo, account }: MessageAccountProps) => { + const { oid: userOid } = useValueRecoilState("loginInfo") || {}; + const [isOpenPayment, setIsOpenPayment] = useState(false); const [bankName, accountNumber] = useMemo((): [string, string] => { const splited = account.split(" "); return [splited?.[0] || "", splited?.[1] || ""]; }, [account]); + const settlementStatusForMe = useMemo( + () => + roomInfo && + roomInfo.part.filter((user) => user._id === userOid)?.[0]?.isSettlement, + [userOid, roomInfo] + ); const style = { width: "210px" }; const styleHead = { @@ -54,12 +65,27 @@ const MessageAccount = ({ account }: MessageAccountProps) => {
- - - + {settlementStatusForMe === "paid" ? ( + + ) : // @todo: 정산현황 + settlementStatusForMe === "sent" ? ( + + ) : ( + + )}
+
); }; diff --git a/src/components/Chat/MessagesBody/MessageSet/index.tsx b/src/components/Chat/MessagesBody/MessageSet/index.tsx index f3a8104a8..e603f71ef 100644 --- a/src/components/Chat/MessagesBody/MessageSet/index.tsx +++ b/src/components/Chat/MessagesBody/MessageSet/index.tsx @@ -21,7 +21,7 @@ import { ReactComponent as TaxiIcon } from "static/assets/TaxiAppIcon.svg"; type MessageBodyProps = { type: (UserChat | BotChat)["type"]; content: (UserChat | BotChat)["content"]; - roomInfo?: BotChat["roomInfo"]; + roomInfo: Room; color: CSS["color"]; }; @@ -35,11 +35,7 @@ const MessageBody = ({ type, content, roomInfo, color }: MessageBodyProps) => { case "settlement": return ; case "account": - return ; - } - - if (!roomInfo) return null; - switch (type) { + return ; case "share": return ; default: @@ -50,9 +46,10 @@ const MessageBody = ({ type, content, roomInfo, color }: MessageBodyProps) => { type MessageSetProps = { chats: Array; layoutType: LayoutType; + roomInfo: Room; }; -const MessageSet = ({ chats, layoutType }: MessageSetProps) => { +const MessageSet = ({ chats, layoutType, roomInfo }: MessageSetProps) => { const { oid: userOid } = useValueRecoilState("loginInfo") || {}; const authorId = chats?.[0]?.authorId; const authorProfileUrl = @@ -156,7 +153,7 @@ const MessageSet = ({ chats, layoutType }: MessageSetProps) => {
diff --git a/src/components/Chat/MessagesBody/index.tsx b/src/components/Chat/MessagesBody/index.tsx index 7c10be0c1..339b55d26 100644 --- a/src/components/Chat/MessagesBody/index.tsx +++ b/src/components/Chat/MessagesBody/index.tsx @@ -8,11 +8,12 @@ import LoadingChats from "./LoadingChats"; type MessagesBodyProps = { layoutType: LayoutType; + roomInfo: Room; chats: Chats; }; const MessagesBody = ( - { layoutType, chats: _chats }: MessagesBodyProps, + { layoutType, roomInfo, chats: _chats }: MessagesBodyProps, ref: ForwardedRef ) => (
{_chats.length <= 0 && } - {useChatsForBody(_chats, layoutType)} + {useChatsForBody(_chats, layoutType, roomInfo)}
); diff --git a/src/components/Chat/index.tsx b/src/components/Chat/index.tsx index 1a1badd37..81f14f205 100644 --- a/src/components/Chat/index.tsx +++ b/src/components/Chat/index.tsx @@ -61,12 +61,14 @@ const Chat = ({ roomId, layoutType }: ChatProps) => {
void; + className?: string; // for emotion (css props) +} & Parameters[0]; + +const syncValue2Account = (value: string): [string, string] => { + const splited = value.split(" "); + const name: string = bankNames.includes(splited?.[0]) + ? splited?.[0] + : bankNames[0]; + const number: string = (splited?.[1] || "").replace(/[^0-9]/g, ""); + return [name, number]; +}; + +const syncAccount2Value = (_name: string, _number: string): string => { + if (_number === "") return ""; + const name: string = bankNames.includes(_name) ? _name : bankNames[0]; + const number: string = _number.replace(/[^0-9]/g, ""); + return name + " " + number; +}; + +const InputAcount = ({ + value, + onChangeValue, + className, + ...inputProps +}: InputAcountProps) => { + const [_name, number] = useMemo(() => syncValue2Account(value), [value]); + const [name, setName] = useState(_name); + + useEffect(() => { + if (number !== "") setName(_name); + }, [_name, number]); + + const onChangeName = (nameAfter: string) => { + setName(nameAfter); + onChangeValue?.(syncAccount2Value(nameAfter, number)); + }; + const onChangeNumber = (numberAfter: string) => { + onChangeValue?.(syncAccount2Value(name, numberAfter)); + }; + + return ( + + + + ); +}; + +export default InputAcount; diff --git a/src/components/Input/Select.tsx b/src/components/Input/Select.tsx new file mode 100644 index 000000000..f3fc625bc --- /dev/null +++ b/src/components/Input/Select.tsx @@ -0,0 +1,62 @@ +import theme from "tools/theme"; + +import ArrowDropDownRoundedIcon from "@mui/icons-material/ArrowDropDownRounded"; + +type SelectProps = { + value: string; + options: Array<{ value: string; label: string }>; + onChangeValue?: (v: string) => void; + className?: string; // for emotion (css props) +}; + +const Select = ({ value, options, onChangeValue, className }: SelectProps) => { + return ( + + + {options.find((option) => option.value === value)?.label || ""} + + + ); +}; + +export default Select; diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index a2c4e985a..47e4f9fa7 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -1,12 +1,19 @@ +import { HTMLProps } from "react"; + import theme from "tools/theme"; type InputProps = { value?: string; onChangeValue?: (v: string) => void; - className?: string; // for emotion -}; + className?: string; // for emotion (css props) +} & HTMLProps; -const Input = ({ value, onChangeValue, className }: InputProps) => ( +const Input = ({ + value, + onChangeValue, + className, + ...inputProps +}: InputProps) => ( onChangeValue?.(e.target.value)} @@ -20,6 +27,7 @@ const Input = ({ value, onChangeValue, className }: InputProps) => ( boxShadow: theme.shadow_purple_input_inset, ...theme.font14, }} + {...inputProps} /> ); diff --git a/src/components/Link/LinkCopy.tsx b/src/components/Link/LinkCopy.tsx index 28c2937cc..9f9020b1c 100644 --- a/src/components/Link/LinkCopy.tsx +++ b/src/components/Link/LinkCopy.tsx @@ -23,7 +23,7 @@ const LinkCopy = ({ children, value, onCopy }: LinkCopyProps) => { return; } navigator.clipboard.writeText(value); - if (onCopy) onCopy(value); + onCopy?.(value); }, [isApp, value, setAlert, onCopy]); return {children}; }; diff --git a/src/components/Link/LinkPayment.tsx b/src/components/Link/LinkPayment.tsx new file mode 100644 index 000000000..f8ea99523 --- /dev/null +++ b/src/components/Link/LinkPayment.tsx @@ -0,0 +1,79 @@ +import { ReactNode, useMemo } from "react"; + +import LinkCopy from "./LinkCopy"; + +type LinkPaymentProps = { + children: ReactNode; + type: "kakaopay" | "toss"; + account?: string; + amount?: number; +}; + +const bankName2Code = (name: Nullable): Nullable => { + if (!name) return undefined; + const bankName2CodeMap = [ + ["농협", "011"], + ["국민", "004"], + ["카카오", "090"], + ["신한", "088"], + ["우리", "020"], + ["기업", "003"], + ["하나", "081"], + ["토스", "092"], + ["새마을", "045"], + ["부산", "032"], + ["대구", "031"], + ["케이", "089"], + ["신협", "048"], + ["우체국", "071"], + ["SC제일", "023"], + ["경남", "039"], + ["수협", "007"], + ["광주", "034"], + ["전북", "037"], + ["저축", "050"], + ["씨티", "027"], + ["제주", "035"], + ["산업", "002"], + ["산림", "064"], + ]; + return ( + bankName2CodeMap.find((item) => name.includes(item[0]))?.[1] || undefined + ); +}; + +const LinkPayment = ({ children, account, type, amount }: LinkPaymentProps) => { + const splited = account?.split(" "); + const bankName = splited?.[0]; + const bankCode = useMemo(() => bankName2Code(bankName), [bankName]); + const accountNumber = splited?.[1]; + + switch (type) { + case "kakaopay": + return ( + { + window.location.href = "kakaotalk://kakaopay/money/to/bank"; + }} + > + {children} + + ); + case "toss": + return ( + + {children} + + ); + } +}; + +export default LinkPayment; diff --git a/src/components/ModalPopup/Body/BodyCallTaxi.tsx b/src/components/ModalPopup/Body/BodyCallTaxi.tsx index f828d1697..09cc760cf 100644 --- a/src/components/ModalPopup/Body/BodyCallTaxi.tsx +++ b/src/components/ModalPopup/Body/BodyCallTaxi.tsx @@ -5,7 +5,7 @@ import LinkCallTaxi from "components/Link/LinkCallTaxi"; import theme from "tools/theme"; import LocationOnRoundedIcon from "@mui/icons-material/LocationOnRounded"; -import { ReactComponent as KakaoTaxiLogo } from "static/assets/KakaotTaxiLogo.svg"; +import { ReactComponent as KakaoTaxiLogo } from "static/assets/KakaoTaxiLogo.svg"; import TmoneyOndaLogo from "static/assets/TmoneyOndaLogo.png"; import { ReactComponent as UTLogo } from "static/assets/UTLogo.svg"; diff --git a/src/components/ModalPopup/ModalChatPayment.tsx b/src/components/ModalPopup/ModalChatPayment.tsx index 811681e1f..1dcd792a3 100644 --- a/src/components/ModalPopup/ModalChatPayment.tsx +++ b/src/components/ModalPopup/ModalChatPayment.tsx @@ -1,8 +1,14 @@ -import { useCallback } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import useAccountFromChats from "hooks/chat/useAccountFromChats"; +import { useValueRecoilState } from "hooks/useFetchRecoilState"; import { useAxios } from "hooks/useTaxiAPI"; import Button from "components/Button"; +import ButtonShare from "components/Button/ButtonShare"; +import DottedLine from "components/DottedLine"; +import LinkCopy from "components/Link/LinkCopy"; +import LinkPayment from "components/Link/LinkPayment"; import Modal from "components/Modal"; import alertAtom from "atoms/alert"; @@ -10,72 +16,158 @@ import { useSetRecoilState } from "recoil"; import theme from "tools/theme"; +import CheckRoundedIcon from "@mui/icons-material/CheckRounded"; +import ContentCopyIcon from "@mui/icons-material/ContentCopy"; +import LocalAtmRoundedIcon from "@mui/icons-material/LocalAtmRounded"; +import { ReactComponent as KakaoPayLogo } from "static/assets/KakaoPayLogo.svg"; +import { ReactComponent as TossLogo } from "static/assets/TossLogo.svg"; + type ModalChatSettlementProps = Omit< Parameters[0], "padding" | "children" | "onEnter" -> & { roomId: Room["_id"]; onRecall?: () => void }; +> & { + roomInfo: Room; + onRecall?: () => void; + account: ReturnType; +}; const ModalChatSettlement = ({ - roomId, + roomInfo, + account, onRecall, ...modalProps }: ModalChatSettlementProps) => { const axios = useAxios(); const setAlert = useSetRecoilState(alertAtom); - - const onClickOk = useCallback( + const { oid: userOid } = useValueRecoilState("loginInfo") || {}; + const isRequesting = useRef(false); + const [isCopied, setIsCopied] = useState(false); + const settlementStatusForMe = useMemo( () => - axios({ - url: "/rooms/commitSettlement", - method: "post", - data: { roomId }, - onSuccess: () => { - modalProps.onChangeIsOpen?.(false); - onRecall?.(); - }, - onError: () => setAlert("송금하기 요청을 실패하였습니다."), - }), - [roomId, modalProps.onChangeIsOpen, onRecall] + roomInfo && + roomInfo.part.filter((user) => user._id === userOid)?.[0]?.isSettlement, + [userOid, roomInfo] ); + const onCopy = useCallback(() => setIsCopied(true), [setIsCopied]); + + useEffect(() => { + if (isCopied) { + const timer = setTimeout(() => setIsCopied(false), 1000); + return () => clearTimeout(timer); + } + }, [isCopied]); - const styleTextCont = { - textAlign: "center" as any, + const onClickOk = () => { + if (isRequesting.current) return; + axios({ + url: "/rooms/commitSettlement", + method: "post", + data: { roomId: roomInfo._id }, + onSuccess: () => { + isRequesting.current = false; + modalProps.onChangeIsOpen?.(false); + onRecall?.(); + }, + onError: () => { + isRequesting.current = false; + setAlert("송금하기를 실패하였습니다."); + }, + }); }; - const styleTextCont2 = { - textAlign: "center" as any, - lineHieght: "12px", - paddingTop: "6px", - fontSize: "10px", - color: "888888", + + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", }; - const styleTxt1 = { - fontSize: "16px", - fontWeight: "bold", + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", }; - const styleTxt2 = { - fontSize: "16px", - fontWeight: 300, + const styleText = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px 12px", }; - const styleTxt3 = { - fontSize: "16px", - fontWeight: "bold", - color: "#6E3678", + const styleAccount = { + ...theme.font14, + display: "flex", + justifyContent: "center", + gap: "12px", + margin: "0 8px 12px", + color: theme.gray_text, }; return ( - -
-
- 송금 - - 완료 - 하시겠습니까? -
-
- 택시비 결제자에게 송금 후 완료해주세요. -
- 완료 후 취소는 불가능합니다. -
+ +
+ + 송금하기 +
+ {account && ( + <> +
+ 택시비 결제자의 계좌번호를 참고하시어 송금해 주세요. 결제 방법 선택 + 시에 해당 앱으로 이동합니다. +
+
+ 계좌번호 +
{account}
+
+
+ + + ) : ( + + ) + } + background={theme.gray_background} + /> + + + } + background="#FFEB00" + /> + + + } + background="#0050FF" + /> + +
+ +
+ + )} +
+ 택시비 결제자에게 송금 후 송금 확인 버튼을 눌러주세요. +
+
+ • 완료 후 취소는 불가능합니다. +
+ + • 결제자에게 금액을 송금한 경우에만 진행해주세요. +
- 완료하기 + {settlementStatusForMe === "send-required" + ? "송금 확인" + : settlementStatusForMe === "sent" + ? "송금 확인 완료" + : "송금 확인 불가능"}
diff --git a/src/components/ModalPopup/ModalReportInChatting.tsx b/src/components/ModalPopup/ModalChatReporUser.tsx similarity index 97% rename from src/components/ModalPopup/ModalReportInChatting.tsx rename to src/components/ModalPopup/ModalChatReporUser.tsx index 9601d37fa..164ffca20 100644 --- a/src/components/ModalPopup/ModalReportInChatting.tsx +++ b/src/components/ModalPopup/ModalChatReporUser.tsx @@ -17,7 +17,7 @@ import theme from "tools/theme"; import ArrowDropDownRoundedIcon from "@mui/icons-material/ArrowDropDownRounded"; import EditRoundedIcon from "@mui/icons-material/EditRounded"; -type ModalReportInChattingProps = { +type ModalChatReporUserProps = { isOpen: boolean; onChangeIsOpen: (isOpen: boolean) => void; path: string; @@ -25,13 +25,13 @@ type ModalReportInChattingProps = { reportedId: string; }; -const ModalReportInChatting = ({ +const ModalChatReporUser = ({ isOpen, onChangeIsOpen, path, name, reportedId, -}: ModalReportInChattingProps) => { +}: ModalChatReporUserProps) => { const axios = useAxios(); const [type, setType] = useState("no-settlement"); const [isSubmitted, setIsSubmitted] = useState(false); @@ -253,4 +253,4 @@ const ModalReportInChatting = ({ ); }; -export default ModalReportInChatting; +export default ModalChatReporUser; diff --git a/src/components/ModalPopup/ModalChatSaveAccount.tsx b/src/components/ModalPopup/ModalChatSaveAccount.tsx new file mode 100644 index 000000000..79fde84d1 --- /dev/null +++ b/src/components/ModalPopup/ModalChatSaveAccount.tsx @@ -0,0 +1,125 @@ +import { useCallback, useEffect, useState } from "react"; + +import { + useFetchRecoilState, + useValueRecoilState, +} from "hooks/useFetchRecoilState"; +import { useAxios } from "hooks/useTaxiAPI"; + +import Button from "components/Button"; +import InputAcount from "components/Input/InputAccount"; +import Modal from "components/Modal"; + +import alertAtom from "atoms/alert"; +import { useSetRecoilState } from "recoil"; + +import theme from "tools/theme"; + +import WalletIcon from "@mui/icons-material/Wallet"; + +type ModalChatSaveAcountProps = Omit< + Parameters[0], + "padding" | "children" | "onEnter" +> & { account?: string }; + +const ModalChatSaveAcount = ({ + account: accountDefault = "", + ...modalProps +}: ModalChatSaveAcountProps) => { + const axios = useAxios(); + const setAlert = useSetRecoilState(alertAtom); + const { account: accountOrigin } = useValueRecoilState("loginInfo") || {}; + const [account, setAccount] = useState(accountDefault || ""); + const fetchLoginInfo = useFetchRecoilState("loginInfo"); + + useEffect(() => setAccount(accountDefault || ""), [accountDefault]); + + const onSubmit = useCallback(() => { + modalProps.onChangeIsOpen?.(false); + axios({ + url: "/users/editAccount", + method: "post", + data: { account }, + onSuccess: () => fetchLoginInfo(), + onError: () => setAlert("계좌번호 저장을 실패하였습니다."), + }); + }, [account]); + + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + const styleText = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px 12px", + }; + + return ( + +
+ + 계좌번호 저장 +
+
+ {!accountOrigin || accountOrigin === "" + ? "계좌번호를 저장하면 다음번 정산하기에서는 자동 입력되어 다시 계좌번호를 입력할 필요가 없어요. 계좌번호를 저장할까요?" + : "저장된 계좌번호와 다른 정보가 정산하기에 입력되었어요. 저장된 정보를 입력한 계좌번호로 수정할까요?"} +
+
+ 계좌번호 + +
+
+ + +
+
+ ); +}; + +export default ModalChatSaveAcount; diff --git a/src/components/ModalPopup/ModalChatSettlement.tsx b/src/components/ModalPopup/ModalChatSettlement.tsx index 582a156ff..9f02f0e85 100644 --- a/src/components/ModalPopup/ModalChatSettlement.tsx +++ b/src/components/ModalPopup/ModalChatSettlement.tsx @@ -1,14 +1,18 @@ -import { useCallback } from "react"; +import { useMemo, useRef, useState } from "react"; +import useSendMessage from "hooks/chat/useSendMessage"; +import { useValueRecoilState } from "hooks/useFetchRecoilState"; import { useAxios } from "hooks/useTaxiAPI"; import Button from "components/Button"; import DottedLine from "components/DottedLine"; +import InputAcount from "components/Input/InputAccount"; import Modal from "components/Modal"; import alertAtom from "atoms/alert"; import { useSetRecoilState } from "recoil"; +import regExpTest from "tools/regExpTest"; import theme from "tools/theme"; import AccountBalanceWalletRoundedIcon from "@mui/icons-material/AccountBalanceWalletRounded"; @@ -16,30 +20,49 @@ import AccountBalanceWalletRoundedIcon from "@mui/icons-material/AccountBalanceW type ModalChatSettlementProps = Omit< Parameters[0], "padding" | "children" | "onEnter" -> & { roomId: Room["_id"]; onRecall?: () => void }; +> & { + roomInfo: Room; + onRecall?: () => void; + openSaveAccountModal?: (account: string) => void; +}; const ModalChatSettlement = ({ - roomId, + roomInfo, onRecall, + openSaveAccountModal, ...modalProps }: ModalChatSettlementProps) => { const axios = useAxios(); const setAlert = useSetRecoilState(alertAtom); + const { account: defaultAccount } = useValueRecoilState("loginInfo") || {}; + const [account, setAccount] = useState(defaultAccount || ""); + const isValidAccount = useMemo(() => regExpTest.account(account), [account]); + const isRequesting = useRef(false); + const sendMessage = useSendMessage(roomInfo._id, isRequesting); - const onClickOk = useCallback( - () => - axios({ - url: "/rooms/commitPayment", - method: "post", - data: { roomId }, - onSuccess: () => { - modalProps.onChangeIsOpen?.(false); - onRecall?.(); - }, - onError: () => setAlert("정산하기 요청을 실패하였습니다."), - }), - [roomId, modalProps.onChangeIsOpen, onRecall] - ); + const onClickOk = () => { + if (isRequesting.current || !isValidAccount) return; + isRequesting.current = true; + axios({ + url: "/rooms/commitPayment", + method: "post", + data: { roomId: roomInfo._id }, + onSuccess: async () => { + isRequesting.current = false; + onRecall?.(); + if (account !== "") { + await sendMessage("account", { text: account }); + isRequesting.current = false; + if (account !== defaultAccount) openSaveAccountModal?.(account); + } + modalProps.onChangeIsOpen?.(false); + }, + onError: () => { + isRequesting.current = false; + setAlert("정산하기를 실패했습니다."); + }, + }); + }; const styleTitle = { ...theme.font18, @@ -56,29 +79,25 @@ const ModalChatSettlement = ({ color: theme.gray_text, margin: "0 8px 12px", }; - // const styleTextCont = { - // textAlign: "center" as any, - // }; - // const styleTextCont2 = { - // textAlign: "center" as any, - // lineHieght: "12px", - // paddingTop: "6px", - // fontSize: "10px", - // color: "888888", - // }; - // const styleTxt1 = { - // fontSize: "16px", - // fontWeight: "bold", - // }; - // const styleTxt2 = { - // fontSize: "16px", - // fontWeight: 300, - // }; - // const styleTxt3 = { - // fontSize: "16px", - // fontWeight: "bold", - // color: "#6E3678", - // }; + const styleAccount = { + margin: "12px 8px", + display: "flex", + alignItems: "center", + color: theme.gray_text, + whiteSpace: "nowrap", + ...theme.font14, + } as const; + const styleButtons = { + position: "relative", + display: "flex", + justifyContent: "space-between", + gap: "10px", + } as const; + const styleAlarm = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px 12px", + }; return ( @@ -95,14 +114,30 @@ const ModalChatSettlement = ({
-
+
+ 계좌번호 + +
+ {isValidAccount ? ( + account === "" ? ( +
+ • 계좌번호를 공유하지 않고 정산 요청을 합니다. +
+ ) : ( +
+ • 정산 요청과 함께 계좌번호를 채팅창에 전송합니다. +
+ ) + ) : ( +
+ • 올바른 계좌번호를 입력해주세요. +
+ )} +
diff --git a/src/components/ModalPopup/ModalMypageModify.tsx b/src/components/ModalPopup/ModalMypageModify.tsx index 39d40ba8a..786fb930e 100644 --- a/src/components/ModalPopup/ModalMypageModify.tsx +++ b/src/components/ModalPopup/ModalMypageModify.tsx @@ -8,10 +8,10 @@ import { } from "hooks/useFetchRecoilState"; import { useAxios } from "hooks/useTaxiAPI"; -import AccountSelector from "components/AccountSelector"; import Button from "components/Button"; import DottedLine from "components/DottedLine"; import Input from "components/Input"; +import InputAccount from "components/Input/InputAccount"; import Modal from "components/Modal"; import ProfileImage from "components/User/ProfileImage"; @@ -246,10 +246,14 @@ const ModalMypageModify = ({ css={{ width: "100%", marginLeft: "10px" }} />
- +
+ {t("account")} + +
만석인 방 포함하기
-
- -
- {props.sortOption} - -
-
+ void} + css={styleSelect} + /> +
+ {type === "etc-reason" && ( +
+ +