From 2ac90d9e21ed694a7e85f4f3d7fce80984989d9a Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sat, 22 Apr 2023 17:28:11 +0900 Subject: [PATCH 1/7] Add: LinkKakaotalkShare --- src/components/Link/LinkKakaotalkShare.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/Link/LinkKakaotalkShare.tsx b/src/components/Link/LinkKakaotalkShare.tsx index 64203bcb8..1981ad1cd 100644 --- a/src/components/Link/LinkKakaotalkShare.tsx +++ b/src/components/Link/LinkKakaotalkShare.tsx @@ -9,6 +9,7 @@ type LinkKakaotalkShareProps = { description?: string; buttonText?: string; buttonTo?: string; + partNum?: number; }; const LinkKakaotalkShare = ({ @@ -17,6 +18,7 @@ const LinkKakaotalkShare = ({ description = "KAIST 구성원들의 택시 동승 인원 모집을 위한 서비스", buttonText = "사이트로 이동", buttonTo: _buttonTo, + partNum, }: LinkKakaotalkShareProps) => { const { pathname, search } = useLocation(); const buttonTo = _buttonTo ?? pathname + search; @@ -35,7 +37,7 @@ const LinkKakaotalkShare = ({ }, []); const onClick = useCallback(() => { const kakao = window.Kakao; - const webUrl = "https://taxi.sparcs.org"; + const webUrl = "https://taxi.dev.sparcs.org"; if (!kakao) { console.error("Kakao SDK is not loaded."); return; @@ -53,8 +55,11 @@ const LinkKakaotalkShare = ({ title, description, imageUrl: `${webUrl}/graph.png`, - link: { webUrl, mobileWebUrl: webUrl }, + imageWidth: 1024, + imageHeight: 500, + link: { webUrl, mobileWebUrl: webUrl }, // TODO : androidExecutionParams, iosExecutionParams 설정 }, + social: partNum && { subscriberCount: partNum }, buttons: [ { title: buttonText, From da5a8887ecafd359054492aab4e99a7167f7c4e8 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sun, 23 Apr 2023 03:16:43 +0900 Subject: [PATCH 2/7] Add: ModalRoomShare --- src/components/Link/LinkKakaotalkShare.tsx | 4 +- src/components/ModalPopup/ModalRoomShare.tsx | 147 ++++++++++++++++++ .../MessageForm/Popup/PopupAccount.tsx | 8 +- src/static/assets/KakaoTalkLogo.svg | 5 + 4 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/components/ModalPopup/ModalRoomShare.tsx create mode 100644 src/static/assets/KakaoTalkLogo.svg diff --git a/src/components/Link/LinkKakaotalkShare.tsx b/src/components/Link/LinkKakaotalkShare.tsx index 1981ad1cd..abce572c3 100644 --- a/src/components/Link/LinkKakaotalkShare.tsx +++ b/src/components/Link/LinkKakaotalkShare.tsx @@ -59,7 +59,7 @@ const LinkKakaotalkShare = ({ imageHeight: 500, link: { webUrl, mobileWebUrl: webUrl }, // TODO : androidExecutionParams, iosExecutionParams 설정 }, - social: partNum && { subscriberCount: partNum }, + social: { subscriberCount: partNum ?? 0 }, buttons: [ { title: buttonText, @@ -70,7 +70,7 @@ const LinkKakaotalkShare = ({ }, ], }); - }, [title, description, buttonTo]); + }, [title, description, buttonText, buttonTo, partNum]); return
{children}
; }; diff --git a/src/components/ModalPopup/ModalRoomShare.tsx b/src/components/ModalPopup/ModalRoomShare.tsx new file mode 100644 index 000000000..7bb102e9a --- /dev/null +++ b/src/components/ModalPopup/ModalRoomShare.tsx @@ -0,0 +1,147 @@ +import { useCallback, useEffect, useState } from "react"; + +import DottedLine from "components/DottedLine"; +import LinkKakaotalkShare from "components/Link/LinkKakaotalkShare"; +import Modal from "components/Modal"; + +import alertAtom from "atoms/alert"; +import { useSetRecoilState } from "recoil"; + +import theme from "tools/theme"; + +import CheckIcon from "@mui/icons-material/Check"; +import ContentCopyIcon from "@mui/icons-material/ContentCopy"; +import ShareIcon from "@mui/icons-material/Share"; +import { ReactComponent as KakaoTalkLogo } from "static/assets/KakaoTalkLogo.svg"; + +type ButtonShareProps = { + text: string; + icon: React.ReactNode; + background: string; + onClick?: () => void; +}; +type ModalRoomShareProps = { + isOpen: boolean; + onChangeIsOpen?: (isOpen: boolean) => void; + roomId: string; +}; + +const ButtonShare = ({ text, icon, background, onClick }: ButtonShareProps) => { + return ( +
+
+ {icon} +
+
+ {text} +
+
+ ); +}; + +const ModalRoomShare = ({ + isOpen, + onChangeIsOpen, + roomId, +}: ModalRoomShareProps) => { + const { host } = window.location; + const pathForShare = `/invite/${roomId}`; + + const setAlert = useSetRecoilState(alertAtom); + const [isCopied, setIsCopied] = useState(false); + + useEffect(() => { + if (isCopied) { + const timer = setTimeout(() => setIsCopied(false), 1000); + return () => clearTimeout(timer); + } + }, [isCopied]); + + const handleCopy = useCallback(() => { + if (!navigator.clipboard) { + setAlert("복사를 지원하지 않는 브라우저입니다."); + return; + } + navigator.clipboard.writeText(host + pathForShare); + setIsCopied(true); + }, [pathForShare]); + + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + const styleGuide = { + ...theme.font12, + color: theme.gray_text, + margin: "12px 8px", + }; + const styleBody = { + display: "flex", + gap: "8px", + margin: "12px 8px 0", + }; + + return ( + +
+ 방 공유하기 +
+
방을 여러 사람들에게 공유할 수 있습니다.
+ +
+ + } + background="#FFE812" + /> + + + ) : ( + + ) + } + background={theme.gray_background} + onClick={handleCopy} + /> +
+
+ ); +}; + +export default ModalRoomShare; diff --git a/src/pages/Chatting/MessageForm/Popup/PopupAccount.tsx b/src/pages/Chatting/MessageForm/Popup/PopupAccount.tsx index ed86a3ab1..7265991dd 100644 --- a/src/pages/Chatting/MessageForm/Popup/PopupAccount.tsx +++ b/src/pages/Chatting/MessageForm/Popup/PopupAccount.tsx @@ -25,10 +25,14 @@ const PopupAccount = (props: SendAccoundModalProps) => { 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, @@ -62,7 +66,7 @@ const PopupAccount = (props: SendAccoundModalProps) => { }} >
- + 계좌 보내기
diff --git a/src/static/assets/KakaoTalkLogo.svg b/src/static/assets/KakaoTalkLogo.svg new file mode 100644 index 000000000..3676f494e --- /dev/null +++ b/src/static/assets/KakaoTalkLogo.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From ca2028de9487c219637bb5032dbea68868bea746 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Sun, 23 Apr 2023 22:59:05 +0900 Subject: [PATCH 3/7] Add: Share Button to some components --- package-lock.json | 38 +++ package.json | 1 + src/components/Link/LinkCopy.tsx | 25 ++ src/components/Link/LinkKakaotalkShare.tsx | 8 +- src/components/ModalPopup/ModalRoomSelect.jsx | 169 +++++------ src/components/ModalPopup/ModalRoomShare.tsx | 142 +++++---- src/pages/Chatting/Header/FullChatHeader.jsx | 20 +- src/pages/Chatting/MessagesBody/ChatSet.jsx | 63 ++-- src/pages/Myroom/R2Myroom.jsx | 272 ++++++++++-------- src/routes.ts | 2 +- 10 files changed, 417 insertions(+), 323 deletions(-) create mode 100644 src/components/Link/LinkCopy.tsx diff --git a/package-lock.json b/package-lock.json index 2b50f9510..701f2f74b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "react-dom": "^17.0.1", "react-ga4": "^1.4.1", "react-i18next": "^12.0.0", + "react-qr-code": "^2.0.11", "react-router-dom": "^5.2.0", "recoil": "^0.7.5", "socket.io-client": "^4.3.2", @@ -20408,6 +20409,11 @@ "teleport": ">=0.2.0" } }, + "node_modules/qr.js": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", + "integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==" + }, "node_modules/qs": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", @@ -20760,6 +20766,24 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "peer": true }, + "node_modules/react-qr-code": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.11.tgz", + "integrity": "sha512-P7mvVM5vk9NjGdHMt4Z0KWeeJYwRAtonHTghZT2r+AASinLUUKQ9wfsGH2lPKsT++gps7hXmaiMGRvwTDEL9OA==", + "dependencies": { + "prop-types": "^15.8.1", + "qr.js": "0.0.0" + }, + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x", + "react-native-svg": "*" + }, + "peerDependenciesMeta": { + "react-native-svg": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -43116,6 +43140,11 @@ "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", "peer": true }, + "qr.js": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", + "integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==" + }, "qs": { "version": "6.11.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", @@ -43381,6 +43410,15 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "peer": true }, + "react-qr-code": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.11.tgz", + "integrity": "sha512-P7mvVM5vk9NjGdHMt4Z0KWeeJYwRAtonHTghZT2r+AASinLUUKQ9wfsGH2lPKsT++gps7hXmaiMGRvwTDEL9OA==", + "requires": { + "prop-types": "^15.8.1", + "qr.js": "0.0.0" + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", diff --git a/package.json b/package.json index 33519711c..1edcda604 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-dom": "^17.0.1", "react-ga4": "^1.4.1", "react-i18next": "^12.0.0", + "react-qr-code": "^2.0.11", "react-router-dom": "^5.2.0", "recoil": "^0.7.5", "socket.io-client": "^4.3.2", diff --git a/src/components/Link/LinkCopy.tsx b/src/components/Link/LinkCopy.tsx new file mode 100644 index 000000000..d5a2217e2 --- /dev/null +++ b/src/components/Link/LinkCopy.tsx @@ -0,0 +1,25 @@ +import { useCallback } from "react"; + +import alertAtom from "atoms/alert"; +import { useSetRecoilState } from "recoil"; + +type LinkCopyProps = { + children: React.ReactNode; + value: string; + onCopy?: (value: string) => void; +}; + +const LinkCopy = ({ children, value, onCopy }: LinkCopyProps) => { + const setAlert = useSetRecoilState(alertAtom); + const onClick = useCallback(() => { + if (!navigator.clipboard) { + setAlert("복사를 지원하지 않는 브라우저입니다."); + return; + } + navigator.clipboard.writeText(value); + if (onCopy) onCopy(value); + }, [value, setAlert, onCopy]); + return {children}; +}; + +export default LinkCopy; diff --git a/src/components/Link/LinkKakaotalkShare.tsx b/src/components/Link/LinkKakaotalkShare.tsx index abce572c3..c6c5d67dc 100644 --- a/src/components/Link/LinkKakaotalkShare.tsx +++ b/src/components/Link/LinkKakaotalkShare.tsx @@ -26,7 +26,7 @@ const LinkKakaotalkShare = ({ useEffect(() => { // kakaotalk SDK script 추가 const script = document.createElement("script"); - script.src = "https://developers.kakao.com/sdk/js/kakao.js"; + script.src = "https://t1.kakaocdn.net/kakao_js_sdk/2.1.0/kakao.min.js"; script.async = true; document.body.appendChild(script); @@ -37,7 +37,7 @@ const LinkKakaotalkShare = ({ }, []); const onClick = useCallback(() => { const kakao = window.Kakao; - const webUrl = "https://taxi.dev.sparcs.org"; + const webUrl = "https://taxi.sparcs.org"; if (!kakao) { console.error("Kakao SDK is not loaded."); return; @@ -57,7 +57,7 @@ const LinkKakaotalkShare = ({ imageUrl: `${webUrl}/graph.png`, imageWidth: 1024, imageHeight: 500, - link: { webUrl, mobileWebUrl: webUrl }, // TODO : androidExecutionParams, iosExecutionParams 설정 + link: { webUrl, mobileWebUrl: webUrl }, }, social: { subscriberCount: partNum ?? 0 }, buttons: [ @@ -71,7 +71,7 @@ const LinkKakaotalkShare = ({ ], }); }, [title, description, buttonText, buttonTo, partNum]); - return
{children}
; + return {children}; }; export default LinkKakaotalkShare; diff --git a/src/components/ModalPopup/ModalRoomSelect.jsx b/src/components/ModalPopup/ModalRoomSelect.jsx index 16ace4083..fb991ec96 100644 --- a/src/components/ModalPopup/ModalRoomSelect.jsx +++ b/src/components/ModalPopup/ModalRoomSelect.jsx @@ -22,7 +22,6 @@ import theme from "tools/theme"; import { getLocationName } from "tools/trans"; import ArrowRightAltRoundedIcon from "@mui/icons-material/ArrowRightAltRounded"; -import Tooltip from "@mui/material/Tooltip"; const PlaceSection = (props) => { const style = { @@ -69,51 +68,44 @@ PlaceSection.propTypes = { name: PropTypes.string.isRequired, }; -const InfoSection = (props) => { - const style = { - display: "flex", - flexDirection: "column", - alignItems: props.isAlignLeft ? "flex-start" : "flex-end", - rowGap: "5px", - maxWidth: "fit-content", - flex: props.isBold || props.isColored ? "1 0" : "1 1", - }; - const styleTitle = { - ...theme.font12, - color: theme.gray_text, - }; - const styleText = { - ...theme.font14, - color: props.isColored ? theme.purple : undefined, - fontWeight: props.isBold || props.isColored ? 500 : undefined, - }; - - return ( -
-

{props.title}

-

{props.text}

-
- ); -}; +const InfoSection = (props) => ( +
+

+ {props.title} +

+ {props.children} +
+); InfoSection.propTypes = { title: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - isBold: PropTypes.bool, - isColored: PropTypes.bool, isAlignLeft: PropTypes.bool, + children: PropTypes.node, }; InfoSection.defaultProps = { - isBold: false, - isColored: false, isAlignLeft: true, }; const ModalRoomSelection = (props) => { const { i18n } = useTranslation(); const axios = useAxios(); - const onCall = useRef(false); - const [roomInfo, setRoomInfo] = useState(null); const history = useHistory(); + + const [roomInfo, setRoomInfo] = useState(null); + const onCall = useRef(false); + const [myRooms, setMyRooms] = useRecoilState(myRoomsAtom); const loginInfo = useRecoilValue(loginInfoAtom); const setAlert = useSetRecoilState(alertAtom); @@ -198,88 +190,61 @@ const ModalRoomSelection = (props) => {
- + +

{date2str(roomInfo?.time) ?? ""}

+
- +

+ {roomInfo?.part .reduce((acc, user) => { acc.push(user.nickname); return acc; }, []) - .join(", ") ?? "" - } - /> - + .join(", ") ?? ""} +

+
+ +
+

{`${roomInfo?.part?.length}명`}

+

+  {`/ ${roomInfo?.maxPartLength}명`} +

+
+
- - {isLogin ? ( + {isLogin ? ( + + ) : ( + - ) : ( - - - - )} - + + )} ); }; diff --git a/src/components/ModalPopup/ModalRoomShare.tsx b/src/components/ModalPopup/ModalRoomShare.tsx index 7bb102e9a..f7eaa290f 100644 --- a/src/components/ModalPopup/ModalRoomShare.tsx +++ b/src/components/ModalPopup/ModalRoomShare.tsx @@ -1,13 +1,15 @@ import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import QRCode from "react-qr-code"; import DottedLine from "components/DottedLine"; +import LinkCopy from "components/Link/LinkCopy"; import LinkKakaotalkShare from "components/Link/LinkKakaotalkShare"; import Modal from "components/Modal"; -import alertAtom from "atoms/alert"; -import { useSetRecoilState } from "recoil"; - +import { date2str } from "tools/moment"; import theme from "tools/theme"; +import { getLocationName } from "tools/trans"; import CheckIcon from "@mui/icons-material/Check"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; @@ -20,25 +22,28 @@ type ButtonShareProps = { background: string; onClick?: () => void; }; +type BodyRoomShareProps = { + roomInfo: any; // fixme +}; type ModalRoomShareProps = { isOpen: boolean; onChangeIsOpen?: (isOpen: boolean) => void; - roomId: string; + roomInfo: BodyRoomShareProps["roomInfo"]; }; const ButtonShare = ({ text, icon, background, onClick }: ButtonShareProps) => { return (
{ ); }; -const ModalRoomShare = ({ - isOpen, - onChangeIsOpen, - roomId, -}: ModalRoomShareProps) => { +const BodyRoomShare = ({ roomInfo }: BodyRoomShareProps) => { + const { i18n } = useTranslation(); const { host } = window.location; - const pathForShare = `/invite/${roomId}`; + const pathForShare = `/invite/${roomInfo?._id}`; - const setAlert = useSetRecoilState(alertAtom); const [isCopied, setIsCopied] = useState(false); + const onCopy = useCallback(() => setIsCopied(true), [setIsCopied]); useEffect(() => { if (isCopied) { @@ -82,66 +84,98 @@ const ModalRoomShare = ({ } }, [isCopied]); - const handleCopy = useCallback(() => { - if (!navigator.clipboard) { - setAlert("복사를 지원하지 않는 브라우저입니다."); - return; - } - navigator.clipboard.writeText(host + pathForShare); - setIsCopied(true); - }, [pathForShare]); - - const styleTitle = { - ...theme.font18, - display: "flex", - alignItems: "center", - margin: "0 8px", - }; - const styleIcon = { - fontSize: "21px", - margin: "0 4px 0 0", - }; const styleGuide = { ...theme.font12, color: theme.gray_text, margin: "12px 8px", }; - const styleBody = { + const styleQRSection = { + marginTop: "12px", + position: "relative" as any, + overflow: "hidden", + textAlign: "center" as any, + }; + const styleButtonSection = { display: "flex", - gap: "8px", - margin: "12px 8px 0", + // justifyContent: "center", + gap: "10px", + margin: "12px 0px 0", }; return ( - -
- 방 공유하기 -
+ <>
방을 여러 사람들에게 공유할 수 있습니다.
-
- +
+ +
+
+ } background="#FFE812" /> - - ) : ( - - ) - } - background={theme.gray_background} - onClick={handleCopy} - /> + + + ) : ( + + ) + } + background={theme.gray_background} + /> + +
+ + ); +}; + +const ModalRoomShare = ({ + isOpen, + onChangeIsOpen, + roomInfo, +}: ModalRoomShareProps) => { + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + + return ( + +
+ 방 공유하기
+
); }; export default ModalRoomShare; +export { BodyRoomShare }; diff --git a/src/pages/Chatting/Header/FullChatHeader.jsx b/src/pages/Chatting/Header/FullChatHeader.jsx index 928f7709f..fbaadee1c 100644 --- a/src/pages/Chatting/Header/FullChatHeader.jsx +++ b/src/pages/Chatting/Header/FullChatHeader.jsx @@ -5,6 +5,7 @@ import { useHistory } from "react-router"; import { useR2state } from "hooks/useReactiveState"; import DottedLine from "components/DottedLine"; +import ModalRoomShare from "components/ModalPopup/ModalRoomShare"; import HeaderBody from "./HeaderBody"; @@ -12,12 +13,14 @@ import theme from "tools/theme"; import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import CloseFullscreenRoundedIcon from "@mui/icons-material/CloseFullscreenRounded"; -import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; -import MenuRoundedIcon from "@mui/icons-material/MenuRounded"; +import ShareIcon from "@mui/icons-material/Share"; +import UnfoldLessRoundedIcon from "@mui/icons-material/UnfoldLessRounded"; +import UnfoldMoreRoundedIcon from "@mui/icons-material/UnfoldMoreRounded"; const Header = (props) => { const bodyRef = useRef(); const [isOpen, setOpen] = useState(false); + const [isOpenShare, setIsOpenShare] = useState(false); const [bodyHeight, setBodyHeight] = useState(0); const history = useHistory(); const reactiveState = useR2state(); @@ -105,6 +108,10 @@ const Header = (props) => { {props?.info?.to?.koName}
+ setIsOpenShare(true)} + /> {reactiveState !== 3 && ( { /> )} {isOpen ? ( - setOpen(!isOpen)} /> ) : ( - setOpen(!isOpen)} /> @@ -130,6 +137,11 @@ const Header = (props) => {
+ ); }; diff --git a/src/pages/Chatting/MessagesBody/ChatSet.jsx b/src/pages/Chatting/MessagesBody/ChatSet.jsx index 646fb3b04..746dfdadb 100644 --- a/src/pages/Chatting/MessagesBody/ChatSet.jsx +++ b/src/pages/Chatting/MessagesBody/ChatSet.jsx @@ -1,14 +1,12 @@ import PropTypes from "prop-types"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; +import LinkCopy from "components/Link/LinkCopy"; import ProfileImg from "components/ProfileImg"; import ImageFullscreen from "pages/Chatting/MessagesBody/ImageFullscreen"; import ChatPaySettle from "./ChatPaySettle"; -import alertAtom from "atoms/alert"; -import { useSetRecoilState } from "recoil"; - import hoverEventSet from "tools/hoverEventSet"; import moment from "tools/moment"; import theme from "tools/theme"; @@ -21,10 +19,10 @@ import WalletIcon from "@mui/icons-material/Wallet"; const ChatAccount = (props) => { const bankName = props.account.split(" ")[0]; const accounNumber = props.account.split(" ")[1]; + const [isClicked, setIsClicked] = useState(false); const [isCopied, setIsCopied] = useState(false); - const setAlert = useSetRecoilState(alertAtom); - + const onCopy = useCallback(() => setIsCopied(true), [setIsCopied]); useEffect(() => { if (isCopied) { const timer = setTimeout(() => setIsCopied(false), 1000); @@ -39,14 +37,6 @@ const ChatAccount = (props) => { display: "flex", gap: "6px", }; - const handleCopy = () => { - if (!navigator.clipboard) { - setAlert("복사를 지원하지 않는 브라우저입니다."); - return; - } - navigator.clipboard.writeText(props.account); - setIsCopied(true); - }; return (
{
)} -
{}, setIsClicked)} - > - {isCopied ? ( - - ) : ( - - )} -
+ +
{}, setIsClicked)} + > + {isCopied ? ( + + ) : ( + + )} +
+
{ + const [isOpenShare, setIsOpenShare] = useState(false); const [headerInfoToken, fetchHeaderInfo] = useDateToken(); const [, headerInfo] = useQuery.get(`/rooms/info?id=${props.roomId}`, {}, [ headerInfoToken, ]); - return ; + return ( + +
+ 채팅 창 + setIsOpenShare(true)} + /> + {props.isOpen ? ( + props.onChangeIsOpen(false)} + /> + ) : ( + props.onChangeIsOpen(true)} + /> + )} +
+ {props.isOpen && ( + <> + + + + )} + {headerInfo && ( + + )} +
+ ); }; ChatHeader.propTypes = { + isOpen: PropTypes.bool, + onChangeIsOpen: PropTypes.func, roomId: PropTypes.string, }; @@ -56,134 +108,110 @@ LinkRoom.propTypes = { }; const R2Myroom = (props) => { - const [isHeaderOpen, setHeaderOpen] = useState(true); + const [isOpenHeader, setIsOpenHeader] = useState(true); return ( - <> - - - 내 방 보기 - -
- - 참여 중인 방 -
- - {props.ongoing.length === 0 ? ( - 참여 중인 방이 없습니다 - ) : ( - props.ongoing.map((item) => ( - - - - )) - )} - - - 과거 참여 방 -
- - {props.done.length === 0 ? ( - 과거 참여했던 방이 없습니다 - ) : ( -
- {props.done - .slice( - PAGE_MAX_ITEMS * (props.donePageInfo.currentPage - 1), - PAGE_MAX_ITEMS * props.donePageInfo.currentPage - ) - .map((item) => ( - - - - ))} - + + 내 방 보기 + +
+ + 참여 중인 방 +
+ + {props.ongoing.length === 0 ? ( + 참여 중인 방이 없습니다 + ) : ( + props.ongoing.map((item) => ( + + -
- )} -
-
- - } - right={props.roomId ? <> : null} - /> - {props.roomId ? ( -
- -
- 채팅 창 - {isHeaderOpen ? ( - setHeaderOpen(false)} - /> + + )) + )} + + + 과거 참여 방 +
+ + {props.done.length === 0 ? ( + 과거 참여했던 방이 없습니다 ) : ( - setHeaderOpen(true)} - /> +
+ {props.done + .slice( + PAGE_MAX_ITEMS * (props.donePageInfo.currentPage - 1), + PAGE_MAX_ITEMS * props.donePageInfo.currentPage + ) + .map((item) => ( + + + + ))} + +
)} -
- {isHeaderOpen && ( - <> - - - - )} -
-
- -
-
- ) : null} - + + } + right={ + props.roomId ? ( +
+ +
+ + + +
+
+ ) : null + } + /> ); }; diff --git a/src/routes.ts b/src/routes.ts index 28c1bbb3f..672e74f9c 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -13,7 +13,7 @@ const routes = [ exact: true, }, { - path: ["/", "/home", "/home/:roomId"], + path: ["/", "/home", "/home/:roomId", "/invite/:roomId"], component: lazy(() => import("pages/Home")), exact: true, }, From 9aa2a19d80cc18a7f219dba0e6d8dad1336725e9 Mon Sep 17 00:00:00 2001 From: 14Kgun Date: Mon, 24 Apr 2023 13:51:07 +0900 Subject: [PATCH 4/7] Add: share page to ModalRoomSelection --- .../BodyNotificationGuide.tsx} | 4 +- .../BodyPrivacyPolicy.tsx} | 4 +- .../ModalPopup/Body/BodyRoomSelection.tsx | 237 ++++++++++++++++ .../ModalPopup/Body/BodyRoomShare.tsx | 150 ++++++++++ .../{Terms.tsx => Body/BodyTerms.tsx} | 4 +- .../ModalPopup/ModalNotification.tsx | 4 +- .../ModalPopup/ModalPrivacyPolicy.tsx | 5 +- .../ModalPopup/ModalReport/ReportOption.tsx | 6 +- src/components/ModalPopup/ModalRoomSelect.jsx | 257 ------------------ .../ModalPopup/ModalRoomSelection.tsx | 96 +++++++ src/components/ModalPopup/ModalRoomShare.tsx | 140 +--------- src/components/ModalPopup/ModalTerms.jsx | 5 +- src/components/ModalPopup/index.tsx | 4 + src/components/Navigation.tsx | 69 +++++ src/pages/Chatting/Header/FullChatHeader.jsx | 2 +- src/pages/Chatting/Header/HeaderBody.jsx | 39 +-- src/pages/Home/RoomSection.tsx | 4 +- src/pages/Myroom/R2Myroom.jsx | 21 +- src/pages/Search/SideResult.jsx | 10 +- 19 files changed, 610 insertions(+), 451 deletions(-) rename src/components/ModalPopup/{NotificationGuide.tsx => Body/BodyNotificationGuide.tsx} (95%) rename src/components/ModalPopup/{PrivacyPolicy.tsx => Body/BodyPrivacyPolicy.tsx} (99%) create mode 100644 src/components/ModalPopup/Body/BodyRoomSelection.tsx create mode 100644 src/components/ModalPopup/Body/BodyRoomShare.tsx rename src/components/ModalPopup/{Terms.tsx => Body/BodyTerms.tsx} (99%) delete mode 100644 src/components/ModalPopup/ModalRoomSelect.jsx create mode 100644 src/components/ModalPopup/ModalRoomSelection.tsx create mode 100644 src/components/Navigation.tsx diff --git a/src/components/ModalPopup/NotificationGuide.tsx b/src/components/ModalPopup/Body/BodyNotificationGuide.tsx similarity index 95% rename from src/components/ModalPopup/NotificationGuide.tsx rename to src/components/ModalPopup/Body/BodyNotificationGuide.tsx index 19e126a33..045706931 100644 --- a/src/components/ModalPopup/NotificationGuide.tsx +++ b/src/components/ModalPopup/Body/BodyNotificationGuide.tsx @@ -1,6 +1,6 @@ import theme from "tools/theme"; -const NotificationGuide = () => { +const BodyNotificationGuide = () => { const styleGuide = { ...theme.font12, color: theme.gray_text, @@ -42,4 +42,4 @@ const NotificationGuide = () => { ); }; -export default NotificationGuide; +export default BodyNotificationGuide; diff --git a/src/components/ModalPopup/PrivacyPolicy.tsx b/src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx similarity index 99% rename from src/components/ModalPopup/PrivacyPolicy.tsx rename to src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx index b739b533d..4b734936d 100644 --- a/src/components/ModalPopup/PrivacyPolicy.tsx +++ b/src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx @@ -1,6 +1,6 @@ import theme from "tools/theme"; -const PrivacyPolicy = () => { +const BodyPrivacyPolicy = () => { const styleBox = { padding: "0 24px 0 16px", borderRadius: "10px", @@ -230,4 +230,4 @@ const PrivacyPolicy = () => { ); }; -export default PrivacyPolicy; +export default BodyPrivacyPolicy; diff --git a/src/components/ModalPopup/Body/BodyRoomSelection.tsx b/src/components/ModalPopup/Body/BodyRoomSelection.tsx new file mode 100644 index 000000000..475c61f0e --- /dev/null +++ b/src/components/ModalPopup/Body/BodyRoomSelection.tsx @@ -0,0 +1,237 @@ +import { useCallback, useRef } from "react"; +import { useTranslation } from "react-i18next"; +import { useHistory } from "react-router-dom"; + +import { + useFetchRecoilState, + useValueRecoilState, +} from "hooks/useFetchRecoilState"; +import { useAxios } from "hooks/useTaxiAPI"; + +import Button from "components/Button"; +import DottedLine from "components/DottedLine"; +import LinkLogin from "components/Link/LinkLogin"; +import MiniCircle from "components/MiniCircle"; +import { MAX_PARTICIPATION } from "pages/Myroom"; + +import alertAtom from "atoms/alert"; +import { useSetRecoilState } from "recoil"; + +import { date2str } from "tools/moment"; +import theme from "tools/theme"; +import { getLocationName } from "tools/trans"; + +import ArrowRightAltRoundedIcon from "@mui/icons-material/ArrowRightAltRounded"; + +type PlaceSectionProps = { + type: "from" | "to"; + name: string; +}; +type InfoSectionProps = { + title: string; + alignDirection: "left" | "right"; + children: React.ReactNode; +}; +export type BodyRoomSelectionProps = { + roomInfo: any; // FIXME +}; + +const PlaceSection = ({ type, name }: PlaceSectionProps) => ( +
+ +

+ {type === "from" ? "출발지" : "도착지"} +

+
+

+ {name} +

+
+
+); + +const InfoSection = ({ title, alignDirection, children }: InfoSectionProps) => ( +
+

{title}

+ {children} +
+); + +const BodyRoomSelection = ({ roomInfo }: BodyRoomSelectionProps) => { + const { i18n } = useTranslation(); + const axios = useAxios(); + const history = useHistory(); + + const onCall = useRef(false); + const loginInfo = useValueRecoilState("loginInfo"); + const myRooms = useValueRecoilState("myRooms"); + const fetchMyRooms = useFetchRecoilState("myRooms"); + const setAlert = useSetRecoilState(alertAtom); + + const isLogin = !!loginInfo?.id; // 로그인 여부 + const isRoomFull = roomInfo && roomInfo.part.length >= roomInfo.maxPartLength; // 방이 꽉 찼는지 여부 + const isAlreadyPart = + isLogin && + roomInfo && + (roomInfo.part.some( + (user: BodyRoomSelectionProps["roomInfo"]["part"]) => + user._id === loginInfo.oid + ) ?? + true); // 이미 참여 중인지 여부 + const isMaxPart = + isLogin && myRooms && myRooms.ongoing.length >= MAX_PARTICIPATION; // 최대 참여 가능한 방 개수를 초과했는지 여부 + + const requestJoin = useCallback(async () => { + if (!onCall.current) { + onCall.current = true; + await axios({ + url: "/rooms/join", + method: "post", + data: { roomId: roomInfo._id }, + onSuccess: async () => { + fetchMyRooms(); + history.push(`/myroom/${roomInfo._id}`); + }, + onError: () => setAlert("방 참여에 실패하였습니다."), + }); + onCall.current = false; + } + }, [roomInfo?._id, history]); + + const stylePlace = { + width: "100%", + display: "flex", + justifyContent: "center", + alignItems: "center", + }; + const styleArrow = { + fontSize: "24px", + color: theme.gray_text, + }; + const styleInfoSectionWrapper = { + padding: "16px 14px", + display: "grid", + rowGap: "16px", + }; + const styleMultipleInfo = { + display: "flex", + justifyContent: "space-between", + columnGap: "12px", + }; + + return ( + <> + +
+ + + +
+ +
+ +

{date2str(roomInfo.time) ?? ""}

+
+
+ +

+ {roomInfo.part + .reduce( + (acc: Array, { nickname }: { nickname: string }) => [ + ...acc, + nickname, + ], + [] + ) + .join(", ") ?? ""} +

+
+ +
+

{`${roomInfo?.part?.length}명`}

+

 {`/ ${roomInfo?.maxPartLength}명`}

+
+
+
+
+ {isLogin ? ( + + ) : ( + + + + )} + + ); +}; + +export default BodyRoomSelection; diff --git a/src/components/ModalPopup/Body/BodyRoomShare.tsx b/src/components/ModalPopup/Body/BodyRoomShare.tsx new file mode 100644 index 000000000..48998a805 --- /dev/null +++ b/src/components/ModalPopup/Body/BodyRoomShare.tsx @@ -0,0 +1,150 @@ +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import QRCode from "react-qr-code"; + +import DottedLine from "components/DottedLine"; +import LinkCopy from "components/Link/LinkCopy"; +import LinkKakaotalkShare from "components/Link/LinkKakaotalkShare"; + +import { date2str } from "tools/moment"; +import theme from "tools/theme"; +import { getLocationName } from "tools/trans"; + +import CheckIcon from "@mui/icons-material/Check"; +import ContentCopyIcon from "@mui/icons-material/ContentCopy"; +import { ReactComponent as KakaoTalkLogo } from "static/assets/KakaoTalkLogo.svg"; + +type ButtonShareProps = { + text: string; + icon: React.ReactNode; + background: string; + onClick?: () => void; +}; +export type BodyRoomShareProps = { + roomInfo: any; // fixme + height?: number; +}; + +const ButtonShare = ({ text, icon, background, onClick }: ButtonShareProps) => ( +
+
+ {icon} +
+
+ {text} +
+
+); + +const BodyRoomShare = ({ roomInfo, height }: BodyRoomShareProps) => { + const { i18n } = useTranslation(); + const { host } = window.location; + const pathForShare = `/invite/${roomInfo?._id}`; + + const [isCopied, setIsCopied] = useState(false); + const onCopy = useCallback(() => setIsCopied(true), [setIsCopied]); + + useEffect(() => { + if (isCopied) { + const timer = setTimeout(() => setIsCopied(false), 1000); + return () => clearTimeout(timer); + } + }, [isCopied]); + + const styleWrapper = height + ? { + height, + display: "flex", + flexDirection: "column" as any, + } + : {}; + const styleGuide = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px 12px", + }; + const styleQRSection = { + marginTop: "12px", + position: "relative" as any, + overflow: "hidden", + textAlign: "center" as any, + }; + const styleButtonSection = { + display: "flex", + justifyContent: "center", + gap: "10px", + margin: "12px 0px 0", + }; + + return ( +
+
방을 여러 사람들에게 공유할 수 있습니다.
+ +
+
+ +
+
+
+ + } + background="#FFE812" + /> + + + + ) : ( + + ) + } + background={theme.gray_background} + /> + +
+
+ ); +}; + +export default BodyRoomShare; diff --git a/src/components/ModalPopup/Terms.tsx b/src/components/ModalPopup/Body/BodyTerms.tsx similarity index 99% rename from src/components/ModalPopup/Terms.tsx rename to src/components/ModalPopup/Body/BodyTerms.tsx index d1c57dce1..04ee28dd6 100644 --- a/src/components/ModalPopup/Terms.tsx +++ b/src/components/ModalPopup/Body/BodyTerms.tsx @@ -2,7 +2,7 @@ import { Link } from "react-router-dom"; import theme from "tools/theme"; -const Terms = () => { +const BodyTerms = () => { const styleBox = { padding: "0 24px 0 16px", borderRadius: "10px", @@ -170,4 +170,4 @@ const Terms = () => { ); }; -export default Terms; +export default BodyTerms; diff --git a/src/components/ModalPopup/ModalNotification.tsx b/src/components/ModalPopup/ModalNotification.tsx index f79e06db6..c9d6b51b4 100644 --- a/src/components/ModalPopup/ModalNotification.tsx +++ b/src/components/ModalPopup/ModalNotification.tsx @@ -11,7 +11,7 @@ import DottedLine from "components/DottedLine"; import Modal from "components/Modal"; import Toggle from "components/Toggle"; -import NotificationGuide from "./NotificationGuide"; +import BodyNotificationGuide from "./Body/BodyNotificationGuide"; import theme from "tools/theme"; @@ -177,7 +177,7 @@ const ModalNotification = ({
) : ( - + )} ); diff --git a/src/components/ModalPopup/ModalPrivacyPolicy.tsx b/src/components/ModalPopup/ModalPrivacyPolicy.tsx index 050f8d196..f164ec361 100644 --- a/src/components/ModalPopup/ModalPrivacyPolicy.tsx +++ b/src/components/ModalPopup/ModalPrivacyPolicy.tsx @@ -1,7 +1,8 @@ import { useTranslation } from "react-i18next"; import Modal from "components/Modal"; -import PrivacyPolicy from "components/ModalPopup/PrivacyPolicy"; + +import BodyPrivacyPolicy from "./Body/BodyPrivacyPolicy"; import theme from "tools/theme"; @@ -37,7 +38,7 @@ const ModalPrivacyPolicy = ({ {t("privacy_policy")}
- + ); }; diff --git a/src/components/ModalPopup/ModalReport/ReportOption.tsx b/src/components/ModalPopup/ModalReport/ReportOption.tsx index 6f0c7f369..cd1cf932c 100644 --- a/src/components/ModalPopup/ModalReport/ReportOption.tsx +++ b/src/components/ModalPopup/ModalReport/ReportOption.tsx @@ -19,9 +19,9 @@ const ReportOption = (props: ReportOptionProps) => { const styleButton = (isSelected: boolean) => { return { display: "flex", - ...theme.font10, - borderRadius: "4px", - padding: "3px 6px 3px", + ...theme.font12, + borderRadius: "6px", + padding: "5px 8px", ...theme.cursor(), color: isSelected ? theme.white : theme.gray_text, backgroundColor: isSelected ? theme.purple : theme.gray_background, diff --git a/src/components/ModalPopup/ModalRoomSelect.jsx b/src/components/ModalPopup/ModalRoomSelect.jsx deleted file mode 100644 index fb991ec96..000000000 --- a/src/components/ModalPopup/ModalRoomSelect.jsx +++ /dev/null @@ -1,257 +0,0 @@ -import PropTypes from "prop-types"; -import { useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useHistory } from "react-router-dom"; - -import { useAxios } from "hooks/useTaxiAPI"; - -import Button from "components/Button"; -import DottedLine from "components/DottedLine"; -import LinkLogin from "components/Link/LinkLogin"; -import MiniCircle from "components/MiniCircle"; -import Modal from "components/Modal"; -import { MAX_PARTICIPATION } from "pages/Myroom"; - -import alertAtom from "atoms/alert"; -import loginInfoAtom from "atoms/loginInfo"; -import myRoomsAtom from "atoms/myRooms"; -import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil"; - -import { date2str } from "tools/moment"; -import theme from "tools/theme"; -import { getLocationName } from "tools/trans"; - -import ArrowRightAltRoundedIcon from "@mui/icons-material/ArrowRightAltRounded"; - -const PlaceSection = (props) => { - const style = { - display: "flex", - flexDirection: "column", - justifyContent: "center", - alignItems: "center", - margin: "16px 12px 10px", - flex: "1 1 0", - }; - const stylePlaceType = { - ...theme.font12, - color: theme.gray_text, - margin: "5px 0 1px", - }; - const stylePlaceNameWrapper = { - display: "flex", - justifyContent: "center", - alignItems: "center", - height: "39px", - width: "100%", - }; - const stylePlaceName = { - ...theme.font16_bold, - color: theme.purple, - textAlign: "center", - wordBreak: "keep-all", - }; - - return ( -
- -

- {props.type === "from" ? "출발지" : "도착지"} -

-
-

{props.name}

-
-
- ); -}; -PlaceSection.propTypes = { - type: PropTypes.oneOf(["from", "to"]), - name: PropTypes.string.isRequired, -}; - -const InfoSection = (props) => ( -
-

- {props.title} -

- {props.children} -
-); -InfoSection.propTypes = { - title: PropTypes.string.isRequired, - isAlignLeft: PropTypes.bool, - children: PropTypes.node, -}; -InfoSection.defaultProps = { - isAlignLeft: true, -}; - -const ModalRoomSelection = (props) => { - const { i18n } = useTranslation(); - const axios = useAxios(); - const history = useHistory(); - - const [roomInfo, setRoomInfo] = useState(null); - const onCall = useRef(false); - - const [myRooms, setMyRooms] = useRecoilState(myRoomsAtom); - const loginInfo = useRecoilValue(loginInfoAtom); - const setAlert = useSetRecoilState(alertAtom); - const disableJoinBtn = - roomInfo?.part.some((user) => user._id === loginInfo?.oid) ?? true; - const isRoomFull = roomInfo - ? roomInfo.maxPartLength - roomInfo.part.length === 0 - : false; - const fullParticipation = myRooms?.ongoing.length >= MAX_PARTICIPATION; - const isLogin = !!loginInfo?.id; - - useEffect(() => { - if (props.isOpen) setRoomInfo(props.roomInfo); - }, [props.isOpen]); - - const styleTitle = { - ...theme.font18, - padding: "10px 26px 18px 14px", - }; - const stylePlace = { - width: "100%", - display: "flex", - justifyContent: "center", - alignItems: "center", - }; - const styleArrow = { - fontSize: "24px", - color: theme.gray_text, - }; - const styleInfoSectionWrapper = { - padding: "16px 14px", - display: "grid", - rowGap: "16px", - }; - const styleMultipleInfo = { - display: "flex", - justifyContent: "space-between", - columnGap: "12px", - }; - - const requestJoin = async () => { - if (!onCall.current) { - onCall.current = true; - // FIXME: "/rooms/join" API가 myRoom을 반환하도록 수정 - await axios({ - url: "/rooms/join", - method: "post", - data: { - roomId: roomInfo._id, - }, - onSuccess: async () => { - setMyRooms( - await axios({ - url: "/rooms/searchByUser", - method: "get", - onError: () => - setAlert("예상치 못한 오류가 발생했습니다. 새로고침 해주세요."), - }) - ); - history.push(`/myroom/${roomInfo._id}`); - }, - onError: () => setAlert("방 참여에 실패하였습니다."), - }); - onCall.current = false; - } - }; - - return ( - -
{roomInfo?.name ?? ""}
- -
- - - -
- -
- -

{date2str(roomInfo?.time) ?? ""}

-
-
- -

- {roomInfo?.part - .reduce((acc, user) => { - acc.push(user.nickname); - return acc; - }, []) - .join(", ") ?? ""} -

-
- -
-

{`${roomInfo?.part?.length}명`}

-

-  {`/ ${roomInfo?.maxPartLength}명`} -

-
-
-
-
- {isLogin ? ( - - ) : ( - - - - )} -
- ); -}; -ModalRoomSelection.propTypes = { - isOpen: PropTypes.bool, - onClose: PropTypes.func, - roomInfo: PropTypes.object, -}; - -export default ModalRoomSelection; diff --git a/src/components/ModalPopup/ModalRoomSelection.tsx b/src/components/ModalPopup/ModalRoomSelection.tsx new file mode 100644 index 000000000..eb8d4e361 --- /dev/null +++ b/src/components/ModalPopup/ModalRoomSelection.tsx @@ -0,0 +1,96 @@ +import { useEffect, useMemo, useRef, useState } from "react"; + +import Modal from "components/Modal"; +import Navigation from "components/Navigation"; + +import BodyRoomSelection, { + BodyRoomSelectionProps, +} from "./Body/BodyRoomSelection"; +import { BodyRoomShare } from "./ModalRoomShare"; + +import theme from "tools/theme"; + +type HeightFixWrapperProps = { + children: React.ReactNode; + onChangeHeight?: (height: number) => void; +}; +type ModalRoomSelectionProps = { + isOpen: boolean; + onChangeIsOpen: (isOpen: boolean) => void; + roomInfo: Nullable; // FIXME +}; + +const HeightFixWrapper = ({ + children, + onChangeHeight, +}: HeightFixWrapperProps) => { + const body = useRef(null); + + // resize observer + useEffect(() => { + if (!body.current) return; + const observer = new ResizeObserver(() => { + if (!body.current) return; + if (onChangeHeight) onChangeHeight(body.current.offsetHeight); + console.log(body.current.offsetHeight); + }); + observer.observe(body.current); + return () => observer.disconnect(); + }, []); + + return ( +
+
{children}
+
+ ); +}; + +const ModalRoomSelection = ({ + isOpen, + onChangeIsOpen, + roomInfo: _roomInfo, +}: ModalRoomSelectionProps) => { + const [roomInfo, setRoomInfo] = useState(_roomInfo); + const [bodyHeight, setBodyHeight] = useState(0); + const pages = useMemo( + () => + roomInfo && [ + { + key: "info", + name: "방 정보", + body: , + }, + { + key: "share", + name: "공유하기", + body: , + }, + ], + [roomInfo, bodyHeight] + ); + + useEffect(() => { + if (_roomInfo) setRoomInfo(_roomInfo); + }, [_roomInfo]); + + const styleTitle = { + ...theme.font18, + padding: "10px 8px 12px", + }; + + return ( + + {roomInfo && ( + <> +
{roomInfo.name}
+ + + + + + )} +
+ ); +}; + +export default ModalRoomSelection; diff --git a/src/components/ModalPopup/ModalRoomShare.tsx b/src/components/ModalPopup/ModalRoomShare.tsx index f7eaa290f..0c83143be 100644 --- a/src/components/ModalPopup/ModalRoomShare.tsx +++ b/src/components/ModalPopup/ModalRoomShare.tsx @@ -1,151 +1,17 @@ -import { useCallback, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import QRCode from "react-qr-code"; - -import DottedLine from "components/DottedLine"; -import LinkCopy from "components/Link/LinkCopy"; -import LinkKakaotalkShare from "components/Link/LinkKakaotalkShare"; import Modal from "components/Modal"; -import { date2str } from "tools/moment"; +import BodyRoomShare, { BodyRoomShareProps } from "./Body/BodyRoomShare"; + import theme from "tools/theme"; -import { getLocationName } from "tools/trans"; -import CheckIcon from "@mui/icons-material/Check"; -import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import ShareIcon from "@mui/icons-material/Share"; -import { ReactComponent as KakaoTalkLogo } from "static/assets/KakaoTalkLogo.svg"; -type ButtonShareProps = { - text: string; - icon: React.ReactNode; - background: string; - onClick?: () => void; -}; -type BodyRoomShareProps = { - roomInfo: any; // fixme -}; type ModalRoomShareProps = { isOpen: boolean; onChangeIsOpen?: (isOpen: boolean) => void; roomInfo: BodyRoomShareProps["roomInfo"]; }; -const ButtonShare = ({ text, icon, background, onClick }: ButtonShareProps) => { - return ( -
-
- {icon} -
-
- {text} -
-
- ); -}; - -const BodyRoomShare = ({ roomInfo }: BodyRoomShareProps) => { - const { i18n } = useTranslation(); - const { host } = window.location; - const pathForShare = `/invite/${roomInfo?._id}`; - - const [isCopied, setIsCopied] = useState(false); - const onCopy = useCallback(() => setIsCopied(true), [setIsCopied]); - - useEffect(() => { - if (isCopied) { - const timer = setTimeout(() => setIsCopied(false), 1000); - return () => clearTimeout(timer); - } - }, [isCopied]); - - const styleGuide = { - ...theme.font12, - color: theme.gray_text, - margin: "12px 8px", - }; - const styleQRSection = { - marginTop: "12px", - position: "relative" as any, - overflow: "hidden", - textAlign: "center" as any, - }; - const styleButtonSection = { - display: "flex", - // justifyContent: "center", - gap: "10px", - margin: "12px 0px 0", - }; - - return ( - <> -
방을 여러 사람들에게 공유할 수 있습니다.
- -
- -
-
- - } - background="#FFE812" - /> - - - - ) : ( - - ) - } - background={theme.gray_background} - /> - -
- - ); -}; - const ModalRoomShare = ({ isOpen, onChangeIsOpen, @@ -155,7 +21,7 @@ const ModalRoomShare = ({ ...theme.font18, display: "flex", alignItems: "center", - margin: "0 8px", + margin: "0 8px 12px", }; const styleIcon = { fontSize: "21px", diff --git a/src/components/ModalPopup/ModalTerms.jsx b/src/components/ModalPopup/ModalTerms.jsx index bddfe3710..eadb664ac 100644 --- a/src/components/ModalPopup/ModalTerms.jsx +++ b/src/components/ModalPopup/ModalTerms.jsx @@ -7,7 +7,8 @@ import { useAxios } from "hooks/useTaxiAPI"; import Button from "components/Button"; import { useOnClickLogout } from "components/Link/LinkLogout"; import Modal from "components/Modal"; -import Terms from "components/ModalPopup/Terms"; + +import BodyTerms from "./Body/BodyTerms"; import alertAtom from "atoms/alert"; import loginInfoAtom from "atoms/loginInfo"; @@ -107,7 +108,7 @@ const ModalTerms = (props) => { {t("terms")}
- +
void; +}; +type NavigationProps = { + pages: Array; +}; + +const OptionNavigation = ({ + name, + isSelected, + onClick, +}: ButtonNavigationProps) => ( +
+ {name} +
+); + +const Navigation = ({ pages }: NavigationProps) => { + const [selected, setSelected] = useState(pages?.[0]?.key || ""); + return ( + <> +
+ {pages.map(({ key, name }) => ( + setSelected(key)} + /> + ))} +
+ {pages.find(({ key }) => key === selected)?.body} + + ); +}; + +export default Navigation; diff --git a/src/pages/Chatting/Header/FullChatHeader.jsx b/src/pages/Chatting/Header/FullChatHeader.jsx index fbaadee1c..05bcb443f 100644 --- a/src/pages/Chatting/Header/FullChatHeader.jsx +++ b/src/pages/Chatting/Header/FullChatHeader.jsx @@ -5,7 +5,7 @@ import { useHistory } from "react-router"; import { useR2state } from "hooks/useReactiveState"; import DottedLine from "components/DottedLine"; -import ModalRoomShare from "components/ModalPopup/ModalRoomShare"; +import { ModalRoomShare } from "components/ModalPopup"; import HeaderBody from "./HeaderBody"; diff --git a/src/pages/Chatting/Header/HeaderBody.jsx b/src/pages/Chatting/Header/HeaderBody.jsx index 674fb0a63..9bf374cf9 100644 --- a/src/pages/Chatting/Header/HeaderBody.jsx +++ b/src/pages/Chatting/Header/HeaderBody.jsx @@ -17,25 +17,26 @@ import SendRoundedIcon from "@material-ui/icons/SendRounded"; import LogoutRoundedIcon from "@mui/icons-material/LogoutRounded"; import PaymentRoundedIcon from "@mui/icons-material/PaymentRounded"; -const Info = (props) => { - return ( -
-
- {props.title} -
-
{props.children}
+const Info = (props) => ( +
+
+ {props.title}
- ); -}; +
+ {props.children} +
+
+); Info.propTypes = { title: PropTypes.string, + alignDirection: PropTypes.oneOf(["left", "right"]), children: PropTypes.node, }; @@ -194,8 +195,10 @@ const HeaderBody = (props) => { return (
- {date2str(props.info?.time)} - + + {date2str(props.info?.time)} + + {props.info?.part.length}명 / {props.info?.maxPartLength}명
diff --git a/src/pages/Home/RoomSection.tsx b/src/pages/Home/RoomSection.tsx index 91563efc5..c2c1a87d1 100644 --- a/src/pages/Home/RoomSection.tsx +++ b/src/pages/Home/RoomSection.tsx @@ -4,7 +4,7 @@ import { useHistory } from "react-router-dom"; import useDateToken from "hooks/useDateToken"; import { useQuery } from "hooks/useTaxiAPI"; -import ModalRoomSelection from "components/ModalPopup/ModalRoomSelect"; +import { ModalRoomSelection } from "components/ModalPopup"; import RLayout from "components/RLayout"; import Title from "components/Title"; @@ -60,7 +60,7 @@ const RoomSection = ({ roomId }: RoomSectionProps) => { history.replace("/home")} + onChangeIsOpen={() => history.replace("/home")} roomInfo={roomInfo} /> diff --git a/src/pages/Myroom/R2Myroom.jsx b/src/pages/Myroom/R2Myroom.jsx index b71ede38d..f7f3c0dab 100644 --- a/src/pages/Myroom/R2Myroom.jsx +++ b/src/pages/Myroom/R2Myroom.jsx @@ -7,7 +7,7 @@ import { useQuery } from "hooks/useTaxiAPI"; import DottedLine from "components/DottedLine"; import Empty from "components/Empty"; -import ModalRoomShare from "components/ModalPopup/ModalRoomShare"; +import { ModalRoomShare } from "components/ModalPopup"; import Pagination, { PAGE_MAX_ITEMS } from "components/Pagination"; import RLayout from "components/RLayout"; import Room from "components/Room"; @@ -23,6 +23,7 @@ import UnfoldLessRoundedIcon from "@mui/icons-material/UnfoldLessRounded"; import UnfoldMoreRoundedIcon from "@mui/icons-material/UnfoldMoreRounded"; const ChatHeader = (props) => { + const [isOpen, setIsOpen] = useState(false); const [isOpenShare, setIsOpenShare] = useState(false); const [headerInfoToken, fetchHeaderInfo] = useDateToken(); const [, headerInfo] = useQuery.get(`/rooms/info?id=${props.roomId}`, {}, [ @@ -50,19 +51,19 @@ const ChatHeader = (props) => { }} onClick={() => setIsOpenShare(true)} /> - {props.isOpen ? ( + {isOpen ? ( <UnfoldLessRoundedIcon style={{ color: theme.purple, ...theme.cursor() }} - onClick={() => props.onChangeIsOpen(false)} + onClick={() => setIsOpen(false)} /> ) : ( <UnfoldMoreRoundedIcon style={{ color: theme.purple, ...theme.cursor() }} - onClick={() => props.onChangeIsOpen(true)} + onClick={() => setIsOpen(true)} /> )} </div> - {props.isOpen && ( + {isOpen && ( <> <DottedLine direction="row" margin="16px 0" /> <ChatHeaderBody info={headerInfo} recallEvent={fetchHeaderInfo} /> @@ -80,8 +81,6 @@ const ChatHeader = (props) => { }; ChatHeader.propTypes = { - isOpen: PropTypes.bool, - onChangeIsOpen: PropTypes.func, roomId: PropTypes.string, }; @@ -108,8 +107,6 @@ LinkRoom.propTypes = { }; const R2Myroom = (props) => { - const [isOpenHeader, setIsOpenHeader] = useState(true); - return ( <RLayout.R2 left={ @@ -198,11 +195,7 @@ const R2Myroom = (props) => { zIndex: theme.zIndex_nav - 1, }} > - <ChatHeader - isOpen={isOpenHeader} - onChangeIsOpen={setIsOpenHeader} - roomId={props.roomId} - /> + <ChatHeader roomId={props.roomId} /> <div style={{ height: "100%", minHeight: 0 }}> <WhiteContainer padding="0px" style={{ height: "100%" }}> <SideChat roomId={props.roomId} /> diff --git a/src/pages/Search/SideResult.jsx b/src/pages/Search/SideResult.jsx index 025eecf64..8bdda373c 100644 --- a/src/pages/Search/SideResult.jsx +++ b/src/pages/Search/SideResult.jsx @@ -5,7 +5,7 @@ import usePageFromSearchParams from "hooks/usePageFromSearchParams"; import DottedLine from "components/DottedLine"; import Empty from "components/Empty"; -import ModalRoomSelection from "components/ModalPopup/ModalRoomSelect"; +import { ModalRoomSelection } from "components/ModalPopup"; import Pagination, { PAGE_MAX_ITEMS } from "components/Pagination"; import Room from "components/Room"; import Title from "components/Title"; @@ -164,9 +164,7 @@ const SideResult = (props) => { <div style={{ marginTop: 26 }}> <ModalRoomSelection isOpen={!!selectedRoomInfo} - onClose={() => { - setSelectedRoomInfo(null); - }} + onChangeIsOpen={() => setSelectedRoomInfo(null)} roomInfo={selectedRoomInfo} /> <WhiteContainer padding="20px 20px 22px"> @@ -211,9 +209,7 @@ const SideResult = (props) => { <> <ModalRoomSelection isOpen={!!selectedRoomInfo} - onClose={() => { - setSelectedRoomInfo(null); - }} + onChangeIsOpen={() => setSelectedRoomInfo(null)} roomInfo={selectedRoomInfo} /> <SearchOptions From 357d07e2a9a64e558649d4c204eef94dc61b41bd Mon Sep 17 00:00:00 2001 From: 14Kgun <geon6757@kaist.ac.kr> Date: Mon, 24 Apr 2023 14:53:02 +0900 Subject: [PATCH 5/7] Fix: webUrl hardcoding --- public/index.html | 4 ++++ src/components/Link/LinkKakaotalkShare.tsx | 16 ++-------------- .../ModalPopup/ModalReport/ReportOption.tsx | 1 + 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/public/index.html b/public/index.html index d79ff96f8..25d39a181 100644 --- a/public/index.html +++ b/public/index.html @@ -55,6 +55,10 @@ <meta property="al:web:url" content="안드로이드 앱 URL" /> --> <!-- Script --> + <script + async + src="https://t1.kakaocdn.net/kakao_js_sdk/2.1.0/kakao.min.js" + ></script> <script src="/env.js"></script> <script> document.documentElement.addEventListener( diff --git a/src/components/Link/LinkKakaotalkShare.tsx b/src/components/Link/LinkKakaotalkShare.tsx index c6c5d67dc..e37ff11a7 100644 --- a/src/components/Link/LinkKakaotalkShare.tsx +++ b/src/components/Link/LinkKakaotalkShare.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect } from "react"; +import { useCallback } from "react"; import { useLocation } from "react-router-dom"; import { kakaoSDKKey } from "loadenv"; @@ -23,21 +23,9 @@ const LinkKakaotalkShare = ({ const { pathname, search } = useLocation(); const buttonTo = _buttonTo ?? pathname + search; - useEffect(() => { - // kakaotalk SDK script 추가 - const script = document.createElement("script"); - script.src = "https://t1.kakaocdn.net/kakao_js_sdk/2.1.0/kakao.min.js"; - script.async = true; - document.body.appendChild(script); - - return () => { - // kakaotalk SDK script 제거 - document.body.removeChild(script); - }; - }, []); const onClick = useCallback(() => { const kakao = window.Kakao; - const webUrl = "https://taxi.sparcs.org"; + const { origin: webUrl } = window.location; if (!kakao) { console.error("Kakao SDK is not loaded."); return; diff --git a/src/components/ModalPopup/ModalReport/ReportOption.tsx b/src/components/ModalPopup/ModalReport/ReportOption.tsx index cd1cf932c..3b16dbc91 100644 --- a/src/components/ModalPopup/ModalReport/ReportOption.tsx +++ b/src/components/ModalPopup/ModalReport/ReportOption.tsx @@ -9,6 +9,7 @@ type ReportOptionProps = { onClick: (option: ReportOptionType) => void; }; +// FIXME : components/Navigation 과 통합 가능해보임 const ReportOption = (props: ReportOptionProps) => { const { t } = useTranslation("mypage"); const styleContainer = { From 40c09635496e3d861442a42da8c953a40d82eb7a Mon Sep 17 00:00:00 2001 From: 14Kgun <geon6757@kaist.ac.kr> Date: Mon, 24 Apr 2023 21:10:46 +0900 Subject: [PATCH 6/7] Add: Users to BodyRoomSelection --- .../ModalPopup/Body/BodyRoomSelection.tsx | 64 +++++++++---------- .../ModalPopup/Body/BodyRoomShare.tsx | 2 +- src/components/ModalPopup/ModalModify.jsx | 2 +- .../ModalPopup/ModalRoomSelection.tsx | 1 - src/components/{ => User}/ProfileImg.jsx | 0 src/components/User/Users.tsx | 22 +++++++ src/components/User/index.tsx | 48 ++++++++++++++ src/pages/Chatting/Header/HeaderBody.jsx | 2 +- src/pages/Chatting/MessagesBody/ChatSet.jsx | 2 +- .../Chatting/MessagesBody/PopupReport.tsx | 2 +- src/pages/Mypage/index.tsx | 2 +- src/types/global.d.ts | 6 ++ 12 files changed, 113 insertions(+), 40 deletions(-) rename src/components/{ => User}/ProfileImg.jsx (100%) create mode 100644 src/components/User/Users.tsx create mode 100644 src/components/User/index.tsx diff --git a/src/components/ModalPopup/Body/BodyRoomSelection.tsx b/src/components/ModalPopup/Body/BodyRoomSelection.tsx index 475c61f0e..523ad8340 100644 --- a/src/components/ModalPopup/Body/BodyRoomSelection.tsx +++ b/src/components/ModalPopup/Body/BodyRoomSelection.tsx @@ -12,6 +12,7 @@ import Button from "components/Button"; import DottedLine from "components/DottedLine"; import LinkLogin from "components/Link/LinkLogin"; import MiniCircle from "components/MiniCircle"; +import Users from "components/User/Users"; import { MAX_PARTICIPATION } from "pages/Myroom"; import alertAtom from "atoms/alert"; @@ -81,17 +82,17 @@ const PlaceSection = ({ type, name }: PlaceSectionProps) => ( ); const InfoSection = ({ title, alignDirection, children }: InfoSectionProps) => ( - <div - css={{ - display: "flex", - flexDirection: "column", - alignItems: alignDirection === "left" ? "flex-start" : "flex-end", - rowGap: "5px", - maxWidth: "fit-content", - flexShrink: alignDirection === "left" ? 1 : 0, - }} - > - <p css={{ ...theme.font12, color: theme.gray_text }}>{title}</p> + <div> + <p + css={{ + ...theme.font12, + color: theme.gray_text, + marginBottom: "5px", + textAlign: alignDirection, + }} + > + {title} + </p> {children} </div> ); @@ -154,8 +155,9 @@ const BodyRoomSelection = ({ roomInfo }: BodyRoomSelectionProps) => { }; const styleMultipleInfo = { display: "flex", - justifyContent: "space-between", + overflow: "hidden", columnGap: "12px", + justifyContent: "space-between", }; return ( @@ -178,27 +180,23 @@ const BodyRoomSelection = ({ roomInfo }: BodyRoomSelectionProps) => { <p css={theme.font14_bold}>{date2str(roomInfo.time) ?? ""}</p> </InfoSection> <div css={styleMultipleInfo}> - <InfoSection title="탑승자" alignDirection="left"> - <p css={theme.font14}> - {roomInfo.part - .reduce( - (acc: Array<string>, { nickname }: { nickname: string }) => [ - ...acc, - nickname, - ], - [] - ) - .join(", ") ?? ""} - </p> - </InfoSection> - <InfoSection title="참여 / 최대 인원" alignDirection="right"> - <div css={{ display: "flex" }}> - <p - css={{ ...theme.font14_bold, color: theme.purple }} - >{`${roomInfo?.part?.length}명`}</p> - <p css={theme.font14}> {`/ ${roomInfo?.maxPartLength}명`}</p> - </div> - </InfoSection> + <div css={{ minWidth: 0 }}> + <InfoSection title="탑승자" alignDirection="left"> + <Users values={roomInfo.part} /> + </InfoSection> + </div> + <div css={{ minWidth: "fit-content" }}> + <InfoSection title="참여 / 최대 인원" alignDirection="right"> + <div css={{ display: "flex", justifyContent: "end" }}> + <p + css={{ ...theme.font14_bold, color: theme.purple }} + >{`${roomInfo?.part?.length}명`}</p> + <p css={theme.font14}> +  {`/ ${roomInfo?.maxPartLength}명`} + </p> + </div> + </InfoSection> + </div> </div> </div> {isLogin ? ( diff --git a/src/components/ModalPopup/Body/BodyRoomShare.tsx b/src/components/ModalPopup/Body/BodyRoomShare.tsx index 48998a805..44b5cd530 100644 --- a/src/components/ModalPopup/Body/BodyRoomShare.tsx +++ b/src/components/ModalPopup/Body/BodyRoomShare.tsx @@ -63,7 +63,7 @@ const ButtonShare = ({ text, icon, background, onClick }: ButtonShareProps) => ( const BodyRoomShare = ({ roomInfo, height }: BodyRoomShareProps) => { const { i18n } = useTranslation(); - const { host } = window.location; + const { origin: host } = window.location; const pathForShare = `/invite/${roomInfo?._id}`; const [isCopied, setIsCopied] = useState(false); diff --git a/src/components/ModalPopup/ModalModify.jsx b/src/components/ModalPopup/ModalModify.jsx index 432115fa5..705bd5fa6 100644 --- a/src/components/ModalPopup/ModalModify.jsx +++ b/src/components/ModalPopup/ModalModify.jsx @@ -13,7 +13,7 @@ import AccountSelector from "components/AccountSelector"; import Button from "components/Button"; import DottedLine from "components/DottedLine"; import Modal from "components/Modal"; -import ProfileImg from "components/ProfileImg"; +import ProfileImg from "components/User/ProfileImg"; import alertAtom from "atoms/alert"; import { useSetRecoilState } from "recoil"; diff --git a/src/components/ModalPopup/ModalRoomSelection.tsx b/src/components/ModalPopup/ModalRoomSelection.tsx index eb8d4e361..7efdc32ec 100644 --- a/src/components/ModalPopup/ModalRoomSelection.tsx +++ b/src/components/ModalPopup/ModalRoomSelection.tsx @@ -32,7 +32,6 @@ const HeightFixWrapper = ({ const observer = new ResizeObserver(() => { if (!body.current) return; if (onChangeHeight) onChangeHeight(body.current.offsetHeight); - console.log(body.current.offsetHeight); }); observer.observe(body.current); return () => observer.disconnect(); diff --git a/src/components/ProfileImg.jsx b/src/components/User/ProfileImg.jsx similarity index 100% rename from src/components/ProfileImg.jsx rename to src/components/User/ProfileImg.jsx diff --git a/src/components/User/Users.tsx b/src/components/User/Users.tsx new file mode 100644 index 000000000..b3f216034 --- /dev/null +++ b/src/components/User/Users.tsx @@ -0,0 +1,22 @@ +import User from "."; + +type UsersProps = { + values: Array<User>; +}; + +const Users = ({ values }: UsersProps) => ( + <div + css={{ + display: "flex", + flexWrap: "wrap", + gap: "6px 8px", + overflow: "hidden", + }} + > + {values.map((value) => ( + <User key={value._id} value={value} /> + ))} + </div> +); + +export default Users; diff --git a/src/components/User/index.tsx b/src/components/User/index.tsx new file mode 100644 index 000000000..321546546 --- /dev/null +++ b/src/components/User/index.tsx @@ -0,0 +1,48 @@ +import ProfileImg from "./ProfileImg"; + +import theme from "tools/theme"; + +type UserProps = { + value: User; + // type?: "defalut" | "settlement" | "departed"; +}; + +const User = ({ value }: UserProps) => ( + <div + css={{ + display: "flex", + alignItems: "center", + gap: "4px", + maxWidth: "100%", + }} + > + <div + css={{ + minWidth: "21px", + height: "21px", + overflow: "hidden", + borderRadius: "50%", + background: theme.gray_line, + }} + > + <ProfileImg path={value.profileImageUrl} /> + </div> + <div + css={{ + ...theme.font10, + borderRadius: "6px", + padding: "4px 6px 3px", + boxShadow: theme.shadow_gray_input_inset, + color: theme.gray_text, + background: theme.gray_background, + overflow: "hidden", + whiteSpace: "nowrap", + textOverflow: "ellipsis", + }} + > + {value.nickname} + </div> + </div> +); + +export default User; diff --git a/src/pages/Chatting/Header/HeaderBody.jsx b/src/pages/Chatting/Header/HeaderBody.jsx index 9bf374cf9..f40e02849 100644 --- a/src/pages/Chatting/Header/HeaderBody.jsx +++ b/src/pages/Chatting/Header/HeaderBody.jsx @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import { useMemo, useState } from "react"; -import ProfileImg from "components/ProfileImg"; +import ProfileImg from "components/User/ProfileImg"; import PopupCancel from "./Popup/PopupCancel"; import PopupPay from "./Popup/PopupPay"; diff --git a/src/pages/Chatting/MessagesBody/ChatSet.jsx b/src/pages/Chatting/MessagesBody/ChatSet.jsx index 746dfdadb..6c21ebd23 100644 --- a/src/pages/Chatting/MessagesBody/ChatSet.jsx +++ b/src/pages/Chatting/MessagesBody/ChatSet.jsx @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { useCallback, useEffect, useState } from "react"; import LinkCopy from "components/Link/LinkCopy"; -import ProfileImg from "components/ProfileImg"; +import ProfileImg from "components/User/ProfileImg"; import ImageFullscreen from "pages/Chatting/MessagesBody/ImageFullscreen"; import ChatPaySettle from "./ChatPaySettle"; diff --git a/src/pages/Chatting/MessagesBody/PopupReport.tsx b/src/pages/Chatting/MessagesBody/PopupReport.tsx index ea6419f4d..48dc49452 100644 --- a/src/pages/Chatting/MessagesBody/PopupReport.tsx +++ b/src/pages/Chatting/MessagesBody/PopupReport.tsx @@ -4,7 +4,7 @@ import { useAxios } from "hooks/useTaxiAPI"; import Button from "components/Button"; import Modal from "components/Modal"; -import ProfileImg from "components/ProfileImg"; +import ProfileImg from "components/User/ProfileImg"; import alertAtom from "atoms/alert"; import { useSetRecoilState } from "recoil"; diff --git a/src/pages/Mypage/index.tsx b/src/pages/Mypage/index.tsx index 0dc7999ad..0f7b4f19e 100644 --- a/src/pages/Mypage/index.tsx +++ b/src/pages/Mypage/index.tsx @@ -14,9 +14,9 @@ import { ModalReport, ModalTerms, } from "components/ModalPopup"; -import ProfileImg from "components/ProfileImg"; import SuggestLogin from "components/SuggestLogin"; import Title from "components/Title"; +import ProfileImg from "components/User/ProfileImg"; import WhiteContainer from "components/WhiteContainer"; import Menu from "./Menu"; diff --git a/src/types/global.d.ts b/src/types/global.d.ts index dd805fcc6..c2a13ab7f 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -23,6 +23,12 @@ declare global { enName: string; koName: string; }; + type User = { + _id: string; + name: string; + nickname: string; + profileImageUrl: string; + }; type Room = { name: string; from: Location; From f9a30a3abe362b97c35c7627808249763d33bc6a Mon Sep 17 00:00:00 2001 From: 14Kgun <geon6757@kaist.ac.kr> Date: Mon, 24 Apr 2023 23:08:40 +0900 Subject: [PATCH 7/7] Refactor: ModalReport --- .../ReportList.tsx => Body/BodyReport.tsx} | 42 ++++++----- .../ModalPopup/Body/BodyRoomSelection.tsx | 29 ++++---- .../index.tsx => ModalReport.tsx} | 69 ++++++++++--------- .../ModalPopup/ModalReport/ReportOption.tsx | 52 -------------- src/components/ModalPopup/ModalRoomShare.tsx | 1 - src/pages/Myroom/R2Myroom.jsx | 12 ++-- 6 files changed, 81 insertions(+), 124 deletions(-) rename src/components/ModalPopup/{ModalReport/ReportList.tsx => Body/BodyReport.tsx} (76%) rename src/components/ModalPopup/{ModalReport/index.tsx => ModalReport.tsx} (55%) delete mode 100644 src/components/ModalPopup/ModalReport/ReportOption.tsx diff --git a/src/components/ModalPopup/ModalReport/ReportList.tsx b/src/components/ModalPopup/Body/BodyReport.tsx similarity index 76% rename from src/components/ModalPopup/ModalReport/ReportList.tsx rename to src/components/ModalPopup/Body/BodyReport.tsx index d742c1e46..7c9c7793f 100644 --- a/src/components/ModalPopup/ModalReport/ReportList.tsx +++ b/src/components/ModalPopup/Body/BodyReport.tsx @@ -3,30 +3,38 @@ import { useTranslation } from "react-i18next"; import DottedLine from "components/DottedLine"; import Empty from "components/Empty"; -import { ReportOptionType } from "./ReportOption"; - import { date2str } from "tools/moment"; import theme from "tools/theme"; -type ReportListProps = { - option: ReportOptionType; +type BodyReportProps = { + option: "Reporting" | "Reported"; selectedReportHistory: Array<any>; }; -const ReportList = (props: ReportListProps) => { +const ReportList = (props: BodyReportProps) => { const { t } = useTranslation("mypage"); - const styleBox: CSS = { + + const styleContainer = { + display: "flex", + flexDirection: "column" as any, + overflow: "auto", + borderRadius: "12px", + minHeight: "240px", + height: "calc(100vh - 480px)", + rowGap: "8px", + }; + const styleBox = { display: "flex", - flexDirection: "column", + flexDirection: "column" as any, padding: "10px 12px", borderRadius: "12px", rowGap: "6px", boxShadow: theme.shadow_gray_button_inset, backgroundColor: theme.gray_background, }; - const styleRow: CSS = { + const styleRow = { display: "flex", - flexWrap: "nowrap", + flexWrap: "nowrap" as any, columnGap: "8px", }; const styleProperty = { @@ -34,7 +42,7 @@ const ReportList = (props: ReportListProps) => { color: theme.gray_text, minWidth: "58px", }; - const styleInfo: CSS = { + const styleInfo = { ...theme.font12, }; const getTypeText = (type: string) => { @@ -54,11 +62,11 @@ const ReportList = (props: ReportListProps) => { ); } return ( - <> + <div css={styleContainer}> {props.selectedReportHistory.map((report) => { return ( - <div key={report._id} style={styleBox}> - <div style={styleRow}> + <div key={report._id} css={styleBox}> + <div css={styleRow}> <div style={styleProperty}>{t("page_report.reason")}</div> <div style={{ ...styleInfo, color: theme.purple }}> {getTypeText(report.type)} @@ -66,17 +74,17 @@ const ReportList = (props: ReportListProps) => { </div> <DottedLine direction="row" margin="2px 0" /> {props.option === "Reporting" && ( - <div style={styleRow}> + <div css={styleRow}> <div style={styleProperty}>{t("nickname")}</div> <div style={styleInfo}>{report.reportedId.nickname}</div> </div> )} - <div style={styleRow}> + <div css={styleRow}> <div style={styleProperty}>{t("page_report.date")}</div> <div style={styleInfo}>{date2str(report.time)}</div> </div> {report.type === "etc-reason" && ( - <div style={styleRow}> + <div css={styleRow}> <div style={styleProperty}>{t("page_report.etc_reason")}</div> <div style={styleInfo}>{report.etcDetail}</div> </div> @@ -84,7 +92,7 @@ const ReportList = (props: ReportListProps) => { </div> ); })} - </> + </div> ); }; diff --git a/src/components/ModalPopup/Body/BodyRoomSelection.tsx b/src/components/ModalPopup/Body/BodyRoomSelection.tsx index 523ad8340..fefd7f5a6 100644 --- a/src/components/ModalPopup/Body/BodyRoomSelection.tsx +++ b/src/components/ModalPopup/Body/BodyRoomSelection.tsx @@ -50,7 +50,7 @@ const PlaceSection = ({ type, name }: PlaceSectionProps) => ( > <MiniCircle type={type} /> <p - style={{ + css={{ ...theme.font12, color: theme.gray_text, margin: "5px 0 1px", @@ -122,20 +122,19 @@ const BodyRoomSelection = ({ roomInfo }: BodyRoomSelectionProps) => { isLogin && myRooms && myRooms.ongoing.length >= MAX_PARTICIPATION; // 최대 참여 가능한 방 개수를 초과했는지 여부 const requestJoin = useCallback(async () => { - if (!onCall.current) { - onCall.current = true; - await axios({ - url: "/rooms/join", - method: "post", - data: { roomId: roomInfo._id }, - onSuccess: async () => { - fetchMyRooms(); - history.push(`/myroom/${roomInfo._id}`); - }, - onError: () => setAlert("방 참여에 실패하였습니다."), - }); - onCall.current = false; - } + if (onCall.current) return; + onCall.current = true; + await axios({ + url: "/rooms/join", + method: "post", + data: { roomId: roomInfo._id }, + onSuccess: () => { + fetchMyRooms(); + history.push(`/myroom/${roomInfo._id}`); + }, + onError: () => setAlert("방 참여에 실패하였습니다."), + }); + onCall.current = false; }, [roomInfo?._id, history]); const stylePlace = { diff --git a/src/components/ModalPopup/ModalReport/index.tsx b/src/components/ModalPopup/ModalReport.tsx similarity index 55% rename from src/components/ModalPopup/ModalReport/index.tsx rename to src/components/ModalPopup/ModalReport.tsx index 89bb11a05..de9e95e26 100644 --- a/src/components/ModalPopup/ModalReport/index.tsx +++ b/src/components/ModalPopup/ModalReport.tsx @@ -1,13 +1,13 @@ -import { useState } from "react"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { useQuery } from "hooks/useTaxiAPI"; import DottedLine from "components/DottedLine"; import Modal from "components/Modal"; +import Navigation from "components/Navigation"; -import ReportList from "./ReportList"; -import ReportOption, { ReportOptionType } from "./ReportOption"; +import BodyReport from "./Body/BodyReport"; import theme from "tools/theme"; @@ -24,7 +24,6 @@ type RecordProps = { const ModalReport = (props: RecordProps) => { const { t } = useTranslation("mypage"); - const [option, setOption] = useState<ReportOptionType>("Reporting"); const [, reportHistory] = useQuery.get("/reports/searchByUser"); const styleTitle = { @@ -33,51 +32,57 @@ const ModalReport = (props: RecordProps) => { alignItems: "center", marginBottom: "12px", }; - const styleGuide: CSS = { + const styleGuide = { ...theme.font12, color: theme.gray_text, - wordBreak: "keep-all", + wordBreak: "keep-all" as any, padding: "0 8px 12px", }; const styleLogo = { fontSize: "21px", margin: "0 4px 0 8px", }; - const styleContainer: CSS = { - display: "flex", - flexDirection: "column", - overflow: "auto", - borderRadius: "12px", - minHeight: "240px", - height: "calc(100vh - 480px)", - rowGap: "8px", - }; + + const pages = useMemo( + () => [ + { + key: "Reporting", + name: t("page_report.reported"), + body: ( + <BodyReport + option="Reporting" + selectedReportHistory={reportHistory?.reporting} + /> + ), + }, + { + key: "Reported", + name: t("page_report.received"), + body: ( + <BodyReport + option="Reported" + selectedReportHistory={reportHistory?.reported} + /> + ), + }, + ], + [reportHistory] + ); + return ( <Modal isOpen={props.isOpen} onChangeIsOpen={props.onChangeIsOpen} padding="16px 12px" > - <div style={styleTitle}> + <div css={styleTitle}> <ErrorOutlineRoundedIcon style={styleLogo} /> {t("report_record")} </div> - <div style={styleGuide}>{t("page_report.inquiry")}</div> - <DottedLine direction="row" /> - <ReportOption - option={option} - onClick={(option: ReportOptionType) => setOption(option)} - /> - <div style={styleContainer}> - <ReportList - option={option} - selectedReportHistory={ - option === "Reporting" - ? reportHistory?.reporting - : reportHistory?.reported - } - /> - </div> + <div css={styleGuide}>{t("page_report.inquiry")}</div> + <DottedLine /> + <div css={{ height: "12px" }} /> + <Navigation pages={pages} /> </Modal> ); }; diff --git a/src/components/ModalPopup/ModalReport/ReportOption.tsx b/src/components/ModalPopup/ModalReport/ReportOption.tsx deleted file mode 100644 index 3b16dbc91..000000000 --- a/src/components/ModalPopup/ModalReport/ReportOption.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import theme from "tools/theme"; - -export type ReportOptionType = "Reporting" | "Reported"; - -type ReportOptionProps = { - option: ReportOptionType; - onClick: (option: ReportOptionType) => void; -}; - -// FIXME : components/Navigation 과 통합 가능해보임 -const ReportOption = (props: ReportOptionProps) => { - const { t } = useTranslation("mypage"); - const styleContainer = { - display: "flex", - columnGap: "8px", - padding: "10px 0 12px 8px", - }; - const styleButton = (isSelected: boolean) => { - return { - display: "flex", - ...theme.font12, - borderRadius: "6px", - padding: "5px 8px", - ...theme.cursor(), - color: isSelected ? theme.white : theme.gray_text, - backgroundColor: isSelected ? theme.purple : theme.gray_background, - boxShadow: isSelected - ? theme.shadow_purple_button_inset - : theme.shadow_gray_input_inset, - }; - }; - return ( - <div style={styleContainer}> - <div - style={styleButton(props.option === "Reporting")} - onClick={() => props.onClick("Reporting")} - > - {t("page_report.reported")} - </div> - <div - style={styleButton(props.option === "Reported")} - onClick={() => props.onClick("Reported")} - > - {t("page_report.received")} - </div> - </div> - ); -}; - -export default ReportOption; diff --git a/src/components/ModalPopup/ModalRoomShare.tsx b/src/components/ModalPopup/ModalRoomShare.tsx index 0c83143be..29935dd82 100644 --- a/src/components/ModalPopup/ModalRoomShare.tsx +++ b/src/components/ModalPopup/ModalRoomShare.tsx @@ -30,7 +30,6 @@ const ModalRoomShare = ({ return ( <Modal - width={theme.modal_width} isOpen={isOpen} onChangeIsOpen={onChangeIsOpen} padding="16px 12px 12px" diff --git a/src/pages/Myroom/R2Myroom.jsx b/src/pages/Myroom/R2Myroom.jsx index f7f3c0dab..6ea0efc0f 100644 --- a/src/pages/Myroom/R2Myroom.jsx +++ b/src/pages/Myroom/R2Myroom.jsx @@ -69,13 +69,11 @@ const ChatHeader = (props) => { <ChatHeaderBody info={headerInfo} recallEvent={fetchHeaderInfo} /> </> )} - {headerInfo && ( - <ModalRoomShare - isOpen={isOpenShare} - onChangeIsOpen={setIsOpenShare} - roomInfo={headerInfo} - /> - )} + <ModalRoomShare + isOpen={isOpenShare} + onChangeIsOpen={setIsOpenShare} + roomInfo={headerInfo} + /> </WhiteContainer> ); };