diff --git a/src/components/MyPage/organisms/StatusMenuList/index.tsx b/src/components/MyPage/organisms/StatusMenuList/index.tsx
index c531820f..b72758f9 100644
--- a/src/components/MyPage/organisms/StatusMenuList/index.tsx
+++ b/src/components/MyPage/organisms/StatusMenuList/index.tsx
@@ -1,7 +1,7 @@
import { memo, useRef } from 'react';
import { DefaultData } from '#types/index';
-import Span from '@atoms/Span';
+import { NoService } from '@atoms/icon';
import MenuBtn from '@molecules/MenuBtn';
import ProductItemList from 'src/components/Shop/Organisms/ProductItemList';
import { useDragScroll, useSearch } from 'src/hooks';
@@ -46,7 +46,7 @@ function StatusMenuList(listProps: Props) {
/>
) : (
- 서비스 준비중입니다.
+
)}
>
diff --git a/src/components/MyPage/organisms/UserProfile/style.module.scss b/src/components/MyPage/organisms/UserProfile/style.module.scss
index 03ff7db0..3d6f6396 100644
--- a/src/components/MyPage/organisms/UserProfile/style.module.scss
+++ b/src/components/MyPage/organisms/UserProfile/style.module.scss
@@ -5,7 +5,6 @@
flex-direction: column;
.profile {
padding: 13.5px 0;
- border: 0;
}
}
.total-deal {
diff --git a/src/components/Product/organisms/ProductFooter/index.tsx b/src/components/Product/organisms/ProductFooter/index.tsx
index 47524353..b9cca94a 100644
--- a/src/components/Product/organisms/ProductFooter/index.tsx
+++ b/src/components/Product/organisms/ProductFooter/index.tsx
@@ -3,7 +3,7 @@ import { useState } from 'react';
import { ProductFooterInfo } from '#types/product';
import type { DefaultProps } from '#types/props';
import Button from '@atoms/Button';
-import { ClickHeart, SmallHeart, Time, Views } from '@atoms/icon';
+import { ClickHeart, ClipBoard, SmallHeart, Time, Views } from '@atoms/icon';
import IconText from '@atoms/IconText';
import Span from '@atoms/Span';
import DialogModal from '@templates/DialogModal';
@@ -12,6 +12,7 @@ import useTimeForToday from 'src/hooks/useTimeForToday';
import { toastSuccess } from 'src/utils/toaster';
import $ from './style.module.scss';
+import copyClipBoard from './utils';
type Props = {
footer: ProductFooterInfo;
@@ -24,6 +25,7 @@ export default function ProductFooter(footerProps: Props) {
const [isOpen, setIsOpen] = useState(false);
const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);
+ const handleClipBoard = copyClipBoard();
const handleClickLike = () =>
toastSuccess({ message: '좋아요 기능 준비중입니다.' });
@@ -88,9 +90,18 @@ export default function ProductFooter(footerProps: Props) {
isOpen={isOpen}
title="아래 정보를 통해 연락할 수 있습니다."
content="현재 채팅 기능 준비중이에요. 서비스 준비 전까지 조금만 기다려주세요."
- emphasisContent={contact}
clickText="닫기"
onClick={closeModal}
+ emphasisContent={contact}
+ emphasisIcon={
+
+ }
/>
diff --git a/src/components/Product/organisms/ProductFooter/style.module.scss b/src/components/Product/organisms/ProductFooter/style.module.scss
index d75dca48..ef5b0520 100644
--- a/src/components/Product/organisms/ProductFooter/style.module.scss
+++ b/src/components/Product/organisms/ProductFooter/style.module.scss
@@ -53,3 +53,17 @@
}
}
}
+
+.clip-board {
+ box-sizing: border-box;
+ margin-left: 10px;
+ border: 1.5px solid $gray-280;
+ border-radius: $border-radius;
+ background-color: $white;
+ &:hover {
+ border-color: rgba($primary, 0.7);
+ svg {
+ stroke: rgba($primary, 0.7);
+ }
+ }
+}
diff --git a/src/components/Product/organisms/ProductFooter/utils.ts b/src/components/Product/organisms/ProductFooter/utils.ts
new file mode 100644
index 00000000..4422e760
--- /dev/null
+++ b/src/components/Product/organisms/ProductFooter/utils.ts
@@ -0,0 +1,16 @@
+import { toastSuccess, toastError } from 'src/utils/toaster';
+
+type onCopyFn = (text: string) => void;
+
+function copyClipBoard(): onCopyFn {
+ return async (text: string) => {
+ try {
+ await navigator.clipboard.writeText(text);
+ toastSuccess({ message: '클립보드에 복사되었습니다.' });
+ } catch (error) {
+ toastError({ message: '클립보드 복사에 실패했습니다.' });
+ }
+ };
+}
+
+export default copyClipBoard;
diff --git a/src/components/Product/organisms/ProfileInfo/index.tsx b/src/components/Product/organisms/ProfileInfo/index.tsx
new file mode 100644
index 00000000..3a8a49aa
--- /dev/null
+++ b/src/components/Product/organisms/ProfileInfo/index.tsx
@@ -0,0 +1,33 @@
+import Button from '@atoms/Button';
+import { Share } from '@atoms/icon';
+import Profile from '@molecules/Profile';
+
+import $ from './style.module.scss';
+import { getCurrentUrl, sharePage } from './utils';
+
+type Props = {
+ title: string;
+ userId: number;
+ profileImage: string;
+ name: string;
+};
+
+function ProfileInfo(profileProps: Props) {
+ const { title, userId, profileImage, name } = profileProps;
+ const currentUrl = getCurrentUrl();
+ const handleShare = sharePage({
+ title: `re:Fashion | ${title} | 상품 상세 정보`,
+ url: currentUrl,
+ });
+
+ return (
+
+ );
+}
+
+export default ProfileInfo;
diff --git a/src/components/Product/organisms/ProfileInfo/style.module.scss b/src/components/Product/organisms/ProfileInfo/style.module.scss
new file mode 100644
index 00000000..1ee89d94
--- /dev/null
+++ b/src/components/Product/organisms/ProfileInfo/style.module.scss
@@ -0,0 +1,19 @@
+.profile {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ border-bottom: 1px solid $profile-border;
+}
+
+.share {
+ margin: 0 23px;
+ border: 1.5px solid $gray-280;
+ border-radius: $border-radius;
+ &:hover {
+ border-color: rgba($primary, 0.7);
+ path {
+ fill: rgba($primary, 0.7);
+ }
+ }
+}
diff --git a/src/components/Product/organisms/ProfileInfo/utils.ts b/src/components/Product/organisms/ProfileInfo/utils.ts
new file mode 100644
index 00000000..95c58273
--- /dev/null
+++ b/src/components/Product/organisms/ProfileInfo/utils.ts
@@ -0,0 +1,20 @@
+import copyClipBoard from '../ProductFooter/utils';
+
+export function getCurrentUrl() {
+ if (typeof window !== 'undefined')
+ return `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
+ return '';
+}
+
+export function sharePage(props: { title: string; url: string }): () => void {
+ return async () => {
+ try {
+ navigator.share({
+ title: props.title,
+ url: props.url,
+ });
+ } catch (err) {
+ copyClipBoard()(props.url);
+ }
+ };
+}
diff --git a/src/components/Shop/Organisms/ProductItemList/NoProduct.view.tsx b/src/components/Shop/Organisms/ProductItemList/NoProduct.view.tsx
index 8621330e..aa88ef86 100644
--- a/src/components/Shop/Organisms/ProductItemList/NoProduct.view.tsx
+++ b/src/components/Shop/Organisms/ProductItemList/NoProduct.view.tsx
@@ -1,4 +1,4 @@
-import Span from '@atoms/Span';
+import { NoProduct } from '@atoms/icon';
import $ from './style.module.scss';
@@ -13,7 +13,7 @@ function NoProductView({ isNoProducts, isLoading, isFetching, height }: Props) {
return (
(!isLoading && !isFetching && isNoProducts && (
- 상품 결과가 없습니다.
+
)) ||
null
diff --git a/src/components/Shop/molecules/HeaderTool/index.tsx b/src/components/Shop/molecules/HeaderTool/index.tsx
index dd2f5a17..7ec8391e 100644
--- a/src/components/Shop/molecules/HeaderTool/index.tsx
+++ b/src/components/Shop/molecules/HeaderTool/index.tsx
@@ -53,7 +53,6 @@ function HeaderTool(headerProps: Props) {
{...{ onQueryChange: onClick }}
isGender
hasId
- isSameCodeName
options={data}
selected={selectedMenu}
name="gender"
diff --git a/src/components/shared/atoms/icon/ClipBoard.tsx b/src/components/shared/atoms/icon/ClipBoard.tsx
new file mode 100644
index 00000000..54028228
--- /dev/null
+++ b/src/components/shared/atoms/icon/ClipBoard.tsx
@@ -0,0 +1,24 @@
+import type { IconProps } from '#types/props';
+
+function ClipBoard({ className, style, stroke }: IconProps) {
+ return (
+
+ );
+}
+export { ClipBoard };
diff --git a/src/components/shared/atoms/icon/NoProduct.tsx b/src/components/shared/atoms/icon/NoProduct.tsx
new file mode 100644
index 00000000..44a411df
--- /dev/null
+++ b/src/components/shared/atoms/icon/NoProduct.tsx
@@ -0,0 +1,59 @@
+import type { IconProps } from '#types/props';
+
+function NoProduct({ className, style }: IconProps) {
+ return (
+
+ );
+}
+export { NoProduct };
diff --git a/src/components/shared/atoms/icon/NoService.tsx b/src/components/shared/atoms/icon/NoService.tsx
new file mode 100644
index 00000000..76e9be6a
--- /dev/null
+++ b/src/components/shared/atoms/icon/NoService.tsx
@@ -0,0 +1,67 @@
+import type { IconProps } from '#types/props';
+
+function NoService({ className, style }: IconProps) {
+ return (
+
+ );
+}
+export { NoService };
diff --git a/src/components/shared/atoms/icon/Share.tsx b/src/components/shared/atoms/icon/Share.tsx
new file mode 100644
index 00000000..239a8fa2
--- /dev/null
+++ b/src/components/shared/atoms/icon/Share.tsx
@@ -0,0 +1,20 @@
+import type { IconProps } from '#types/props';
+
+function Share({ className, style, fill }: IconProps) {
+ return (
+
+ );
+}
+export { Share };
diff --git a/src/components/shared/atoms/icon/index.tsx b/src/components/shared/atoms/icon/index.tsx
index 8050e20f..b10b5d5e 100644
--- a/src/components/shared/atoms/icon/index.tsx
+++ b/src/components/shared/atoms/icon/index.tsx
@@ -3,6 +3,7 @@ import { Chat } from './Chat';
import { Check } from './Check';
import { Circle } from './Circle';
import { ClickHeart } from './ClickHeart';
+import { ClipBoard } from './ClipBoard';
import { Close } from './Close';
import { Filter } from './Filter';
import { Google } from './Google';
@@ -11,6 +12,8 @@ import { ImgClose } from './ImgClose';
import { Kakao } from './Kakao';
import { More } from './More';
import { MyPage } from './MyPage';
+import { NoProduct } from './NoProduct';
+import { NoService } from './NoService';
import { OnePiece } from './OnePiece';
import { Pants } from './Pants';
import { Plus } from './Plus';
@@ -20,6 +23,7 @@ import { Search } from './Search';
import { SearchBlack } from './SearchBlack';
import { SelectArrow } from './SelectArrow';
import { Setting } from './Setting';
+import { Share } from './Share';
import { Shop } from './Shop';
import { Skirt } from './Skirt';
import { SmallHeart } from './SmallHeart';
@@ -62,4 +66,8 @@ export {
Home,
Circle,
Star,
+ ClipBoard,
+ Share,
+ NoProduct,
+ NoService,
};
diff --git a/src/components/shared/molecules/Profile/style.module.scss b/src/components/shared/molecules/Profile/style.module.scss
index 143c5b10..9a3042ed 100644
--- a/src/components/shared/molecules/Profile/style.module.scss
+++ b/src/components/shared/molecules/Profile/style.module.scss
@@ -2,7 +2,6 @@
display: flex;
align-items: center;
padding: 12px 16px 12px 23px;
- border-bottom: 1px solid $profile-border;
&-box {
display: flex;
align-items: center;
diff --git a/src/components/shared/templates/DialogModal/index.tsx b/src/components/shared/templates/DialogModal/index.tsx
index 71d8e875..a7957935 100644
--- a/src/components/shared/templates/DialogModal/index.tsx
+++ b/src/components/shared/templates/DialogModal/index.tsx
@@ -1,11 +1,12 @@
-import { memo } from 'react';
+import React, { memo } from 'react';
import Button from '@atoms/Button';
import Span from '@atoms/Span';
-import { URL_REGEX } from '@constants/regExp';
+import { MAILTO, SMS } from '@constants/link';
+import { EMAIL_REGEX, PHONE_REGEX, URL_REGEX } from '@constants/regExp';
import { Modal } from '@templates/Modal';
import classnames from 'classnames';
-import { isSameRegExpCondition } from 'src/utils/regExp';
+import { isSameMultipleRegExp } from 'src/utils/regExp';
import $ from './style.module.scss';
@@ -17,6 +18,7 @@ type Props = {
title?: string;
content?: string | JSX.Element;
emphasisContent?: string;
+ emphasisIcon?: JSX.Element;
clickText?: string;
cancelText?: string;
onCancel?: () => void;
@@ -25,9 +27,16 @@ type Props = {
function DialogModal(dialogProps: Props) {
const { id, label, isOpen, isVerticalBtn, onCancel, onClick } = dialogProps;
- const { title, content, clickText, cancelText, emphasisContent } =
- dialogProps;
- const isUrl = isSameRegExpCondition(URL_REGEX, emphasisContent || '');
+ const { title, content, clickText, cancelText } = dialogProps;
+ const { emphasisContent, emphasisIcon } = dialogProps;
+ const [isUrl, isPhone, isEmail] = isSameMultipleRegExp(
+ [URL_REGEX, PHONE_REGEX, EMAIL_REGEX],
+ emphasisContent || '',
+ );
+ const isContactType = isUrl || isPhone || isEmail;
+ const smsStr = isPhone ? SMS : '';
+ const mailStr = isEmail ? MAILTO : '';
+ const strContent = (smsStr || mailStr) + emphasisContent;
return (
@@ -38,19 +47,22 @@ function DialogModal(dialogProps: Props) {
{title && {title}
}
{content && {content}}
- {emphasisContent &&
- (isUrl ? (
-
- {emphasisContent}
-
- ) : (
- {emphasisContent}
- ))}
+
+ {emphasisContent &&
+ (isContactType ? (
+
+ {emphasisContent}
+
+ ) : (
+
{emphasisContent}
+ ))}
+ {emphasisContent && emphasisIcon && emphasisIcon}
+
{
const isIncludeOtherStatus = includedStatusCodes?.some(
(code) => code === status,
);
- const renderCondition = !redirectUrl || isIncludeOtherStatus;
if (redirectUrl && !isIncludeOtherStatus) {
router.replace(redirectUrl);
}
@@ -94,7 +93,7 @@ class ErrorBoundary extends React.Component
{
return ;
}
- if (hasError && error !== null && renderCondition) {
+ if (hasError && error !== null) {
return errorFallback({
error,
reset: this.resetBoundary,
diff --git a/src/components/shared/templates/Skeleton/Filter/style.module.scss b/src/components/shared/templates/Skeleton/Filter/style.module.scss
index 678b3623..c27743e1 100644
--- a/src/components/shared/templates/Skeleton/Filter/style.module.scss
+++ b/src/components/shared/templates/Skeleton/Filter/style.module.scss
@@ -1,5 +1,6 @@
.skeleton-filter {
display: flex;
+ margin-left: 20px;
.filter {
@include iconLoading();
}
diff --git a/src/constants/link.ts b/src/constants/link.ts
index e3bc9d4c..5ea71b8d 100644
--- a/src/constants/link.ts
+++ b/src/constants/link.ts
@@ -1 +1,4 @@
-export const KAKAO_TALK_ONE_TO_ONE_CHAT = 'http://pf.kakao.com/_jNwnxj/chat';
+const KAKAO_TALK_ONE_TO_ONE_CHAT = 'http://pf.kakao.com/_jNwnxj/chat';
+const MAILTO = 'mailto:';
+const SMS = 'sms:';
+export { KAKAO_TALK_ONE_TO_ONE_CHAT, MAILTO, SMS };
diff --git a/src/constants/regExp.ts b/src/constants/regExp.ts
index c0cb3bd7..ecf2e92e 100644
--- a/src/constants/regExp.ts
+++ b/src/constants/regExp.ts
@@ -1,2 +1,6 @@
-export const URL_REGEX =
- /^(https?:\/\/)?([da-z.-]+).([a-z.]{2,6})([/\w_.#-]*)*$/;
+const URL_REGEX = /^(https?:\/\/)([da-z.-]+).([a-z.]{2,6})([/\w_.#-]*)*$/;
+const PHONE_REGEX = /^\d{3,4}-?\d{3,4}-?\d{4}$/;
+const EMAIL_REGEX =
+ /^[\da-zA-Z]([-_.]?[\da-zA-Z])*@[0-9a-zA-Z]([-_.]?[\da-zA-Z])*.[a-zA-Z]{2,3}$/i;
+
+export { URL_REGEX, PHONE_REGEX, EMAIL_REGEX };
diff --git a/src/hooks/api/login/index.ts b/src/hooks/api/login/index.ts
index 11b7f92a..049f6d0c 100644
--- a/src/hooks/api/login/index.ts
+++ b/src/hooks/api/login/index.ts
@@ -2,6 +2,7 @@ import { useRouter } from 'next/router';
import { postAuthToken, authTest } from 'src/api/login';
import { setAccessToken } from 'src/utils/auth';
+import { getPrevPath } from 'src/utils/pathStorage';
import { toastError, toastSuccess } from 'src/utils/toaster';
import { useCoreMutation, useCoreQuery } from '../core';
@@ -14,7 +15,7 @@ export const usePostAuthToken = () => {
toastSuccess({ message: '로그인되었습니다.' });
const { accessToken } = data.data;
setAccessToken(accessToken);
- router.replace('/');
+ router.replace(getPrevPath());
},
onSettled: (_, error) => {
if (error) {
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 888b05ad..0c2ea536 100755
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -16,6 +16,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import '../styles/globals.scss';
import { useMounted, useWindowResize } from 'src/hooks';
import * as gtag from 'src/lib/gtag';
+import { setPathValue } from 'src/utils/pathStorage';
export type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactElement) => ReactNode;
@@ -54,6 +55,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
useEffect(() => {
const handleRouteChange = (url: string) => {
+ setPathValue(url);
gtag.pageview(url);
};
router.events.on('routeChangeComplete', handleRouteChange);
diff --git a/src/pages/mypage/setting/index.tsx b/src/pages/mypage/setting/index.tsx
index e79a6f1b..c81c5b3d 100644
--- a/src/pages/mypage/setting/index.tsx
+++ b/src/pages/mypage/setting/index.tsx
@@ -42,13 +42,13 @@ function Setting() {
},
{
text: '앱 버전',
- icon: v1.0.1,
+ icon: v1.0.6,
},
{
text: '로그아웃',
onClick: () => {
logoutUtil();
- router.push('/shop');
+ router.push('/');
},
},
{
diff --git a/src/pages/shop/[id]/index.tsx b/src/pages/shop/[id]/index.tsx
index d22602a0..0f929263 100644
--- a/src/pages/shop/[id]/index.tsx
+++ b/src/pages/shop/[id]/index.tsx
@@ -5,7 +5,6 @@ import { useCallback } from 'react';
import HeadMeta from '@atoms/HeadMeta';
import { queryKey } from '@constants/react-query';
import { seoData } from '@constants/seo';
-import Profile from '@molecules/Profile';
import { dehydrate, QueryClient } from '@tanstack/react-query';
import Layout from '@templates/Layout';
import { withGetServerSideProps } from 'src/api/core/withGetServerSideProps';
@@ -16,6 +15,7 @@ import ProductFooter from 'src/components/Product/organisms/ProductFooter';
import ProductImgSlide from 'src/components/Product/organisms/ProductImgSlide';
import ProductNotice from 'src/components/Product/organisms/ProductNotice';
import ProductSize from 'src/components/Product/organisms/ProductSize';
+import ProfileInfo from 'src/components/Product/organisms/ProfileInfo';
import { useProdutDetail } from 'src/hooks/api/product';
import { useSearchStore } from 'src/store/useSearchStore';
@@ -66,7 +66,10 @@ function ShopDetail({ id }: { id: string }) {
-
+
+
diff --git a/src/utils/pathStorage.ts b/src/utils/pathStorage.ts
new file mode 100644
index 00000000..47c706fb
--- /dev/null
+++ b/src/utils/pathStorage.ts
@@ -0,0 +1,15 @@
+export function getPrevPath() {
+ return localStorage.getItem('prevPath') || '/';
+}
+
+export function getCurrentPath() {
+ return localStorage.getItem('currentPath') || '/';
+}
+
+export function setPathValue(currentUrl: string) {
+ const prevPath = getCurrentPath() || '/';
+ if (prevPath !== currentUrl) {
+ localStorage.setItem('prevPath', prevPath);
+ localStorage.setItem('currentPath', currentUrl);
+ }
+}
diff --git a/src/utils/regExp.ts b/src/utils/regExp.ts
index eb807081..cb2cca42 100644
--- a/src/utils/regExp.ts
+++ b/src/utils/regExp.ts
@@ -1,2 +1,6 @@
export const isSameRegExpCondition = (condition: RegExp, str: string) =>
condition.test(str);
+
+export const isSameMultipleRegExp = (regExps: RegExp[], str: string) => {
+ return regExps.map((regExp) => regExp.test(str));
+};