From 9a75843d5db8203e72c4bb6f4e8db14552781a84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=A1=B0=EC=98=88=EC=A7=84?= <orobos654@gmail.com>
Date: Wed, 18 Sep 2024 17:54:20 +0900
Subject: [PATCH] feat: add SharedProfilePage

---
 src/app/routes/share.$key.tsx                 | 40 ++++++++++++++++
 src/pages/profile/ProfilePage.tsx             |  7 +--
 .../SharedProfilePage.module.css              | 40 ++++++++++++++++
 .../SharedProfilePage.stories.tsx             | 16 +++++++
 .../shared_profile/SharedProfilePage.tsx      | 46 +++++++++++++++++++
 .../ui/ImageLayout/ImageLayout.module.css     | 43 +++++++++++++++++
 src/shared/ui/ImageLayout/ImageLayout.tsx     | 11 +++++
 7 files changed, 198 insertions(+), 5 deletions(-)
 create mode 100644 src/app/routes/share.$key.tsx
 create mode 100644 src/pages/shared_profile/SharedProfilePage.module.css
 create mode 100644 src/pages/shared_profile/SharedProfilePage.stories.tsx
 create mode 100644 src/pages/shared_profile/SharedProfilePage.tsx
 create mode 100644 src/shared/ui/ImageLayout/ImageLayout.module.css
 create mode 100644 src/shared/ui/ImageLayout/ImageLayout.tsx

diff --git a/src/app/routes/share.$key.tsx b/src/app/routes/share.$key.tsx
new file mode 100644
index 0000000..2fe1b98
--- /dev/null
+++ b/src/app/routes/share.$key.tsx
@@ -0,0 +1,40 @@
+import { LoaderFunction } from '@remix-run/node';
+import { authenticate } from '../server/authenticate';
+import { getInfoBySharingId } from '../../types';
+import { useLoaderData } from '@remix-run/react';
+import { useMemo } from 'react';
+import { convertDtoToProfile } from '../../entities/profile/model/convertProfileToDto';
+import { MyProfileProvider } from '../../entities/profile/model/myProfileStore';
+import { ProfilePage } from '../../pages/profile/ProfilePage';
+
+export const loader: LoaderFunction = async ({ request, params }) => {
+  const accessToken = await authenticate(request);
+
+  const { key } = params;
+
+  if (!key) {
+    throw new Response('', {
+      status: 404,
+      statusText: 'Not Found',
+    });
+  }
+
+  const { data } = await getInfoBySharingId(key, {
+    headers: {
+      Authorization: `Bearer ${accessToken}`,
+    },
+  });
+
+  return { profile: data };
+};
+
+export default function Page() {
+  const { profile } = useLoaderData<typeof loader>();
+  const profileInitialState = useMemo(() => convertDtoToProfile(profile.userInfo), [profile.userInfo]);
+
+  return (
+    <MyProfileProvider initialState={profileInitialState}>
+      <ProfilePage />
+    </MyProfileProvider>
+  );
+}
diff --git a/src/pages/profile/ProfilePage.tsx b/src/pages/profile/ProfilePage.tsx
index a7e5291..6eaed42 100644
--- a/src/pages/profile/ProfilePage.tsx
+++ b/src/pages/profile/ProfilePage.tsx
@@ -11,6 +11,7 @@ import { useIdealPartnerStore } from 'src/entities/ideal_partner/model/idealPart
 import { Link } from '@remix-run/react';
 import { Header } from 'src/shared/ui/layout/Header/Header';
 import { Theme } from 'src/shared/styles/constants';
+import { ImageLayout } from '../../shared/ui/ImageLayout/ImageLayout';
 
 export const ProfilePage = () => {
   const { ref, inView } = useInView();
@@ -57,11 +58,7 @@ export const ProfilePage = () => {
         )}
       </Header>
       <ScrollView rootClassName={styles.Body}>
-        <div className={styles.ImageLayout} data-itemcount={Math.min(urls.length, 5)}>
-          {urls.map((url) => (
-            <img key={url} src={url} alt={'프로필 이미지'} />
-          ))}
-        </div>
+        <ImageLayout urls={urls} />
         <h1 className={styles.Name} ref={ref}>
           {profile.name}({profile.gender}, {age})
         </h1>
diff --git a/src/pages/shared_profile/SharedProfilePage.module.css b/src/pages/shared_profile/SharedProfilePage.module.css
new file mode 100644
index 0000000..44f34eb
--- /dev/null
+++ b/src/pages/shared_profile/SharedProfilePage.module.css
@@ -0,0 +1,40 @@
+.Wrapper {
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+    height: 100%;
+}
+
+.Header {
+    flex-shrink: 0;
+    width: 100%;
+    display: grid;
+    grid-template-columns: 1fr 3fr 1fr;
+    align-items: center;
+    text-align: center;
+    height: 44px;
+    padding: 0 20px;
+}
+
+.HeaderIconSection {
+    display: flex;
+    gap: 4px;
+    justify-content: end;
+}
+
+.Body {
+    flex-grow: 1;
+    overflow: hidden;
+    position: relative;
+}
+
+.Name {
+    font-size: 24px;
+    font-weight: 600;
+    margin: 24px 0;
+    padding: 0 20px;
+}
+
+.ContentWrapper {
+    padding: 20px 20px 0;
+}
\ No newline at end of file
diff --git a/src/pages/shared_profile/SharedProfilePage.stories.tsx b/src/pages/shared_profile/SharedProfilePage.stories.tsx
new file mode 100644
index 0000000..8f916d6
--- /dev/null
+++ b/src/pages/shared_profile/SharedProfilePage.stories.tsx
@@ -0,0 +1,16 @@
+import { Meta, StoryObj } from '@storybook/react';
+import { SharedProfilePage } from './SharedProfilePage';
+import { MyProfileProvider } from 'src/entities/profile/model/myProfileStore';
+import { fullProfileMock } from '../../entities/profile/api/__mock__/fullProfile.mock';
+
+const meta: Meta<typeof SharedProfilePage> = {
+  component: SharedProfilePage,
+};
+
+export default meta;
+type Story = StoryObj<typeof SharedProfilePage>;
+
+export const Default: Story = {
+  args: {},
+  decorators: [(fn) => <MyProfileProvider initialState={fullProfileMock}>{fn()}</MyProfileProvider>],
+};
diff --git a/src/pages/shared_profile/SharedProfilePage.tsx b/src/pages/shared_profile/SharedProfilePage.tsx
new file mode 100644
index 0000000..64a677d
--- /dev/null
+++ b/src/pages/shared_profile/SharedProfilePage.tsx
@@ -0,0 +1,46 @@
+import { calculateAge, convertDateObjectToDate } from 'src/shared/vo/date';
+import styles from './SharedProfilePage.module.css';
+import { ScrollView } from 'src/shared/ui/ScrollView/ScrollView';
+import { useInView } from 'react-intersection-observer';
+import { useMyProfileStore } from 'src/entities/profile/model/myProfileStore';
+import { MyProfileView } from '../../entities/profile/ui/MyProfile/MyProfile';
+import { Header } from '../../shared/ui/layout/Header/Header';
+import { useTranslation } from 'react-i18next';
+import { ImageLayout } from '../../shared/ui/ImageLayout/ImageLayout';
+
+export const SharedProfilePage = () => {
+  const { t } = useTranslation();
+  const { ref, inView } = useInView();
+
+  const profile = useMyProfileStore((state) => state);
+  const age = calculateAge(convertDateObjectToDate(profile.birthDate));
+
+  const urls = [
+    '/images/googoo_1.png',
+    '/images/googoo_2.gif',
+    '/images/googoo_3.png',
+    '/images/googoo_4.png',
+    '/images/logo.png',
+  ]; // useDataUrlListFromFiles(profile.images);
+
+  return (
+    <div className={styles.Wrapper}>
+      {inView ? (
+        <span />
+      ) : (
+        <Header prefixSlot={<></>} suffixSlot={<></>}>
+          {profile.name}({t(profile.gender)}, {age})
+        </Header>
+      )}
+      <ScrollView rootClassName={styles.Body}>
+        <ImageLayout urls={urls} />
+        <h1 className={styles.Name} ref={ref}>
+          {profile.name}({profile.gender}, {age})
+        </h1>
+        <div className={styles.ContentWrapper}>
+          <MyProfileView profile={profile} initialOpen={true} />
+        </div>
+      </ScrollView>
+    </div>
+  );
+};
diff --git a/src/shared/ui/ImageLayout/ImageLayout.module.css b/src/shared/ui/ImageLayout/ImageLayout.module.css
new file mode 100644
index 0000000..606d64e
--- /dev/null
+++ b/src/shared/ui/ImageLayout/ImageLayout.module.css
@@ -0,0 +1,43 @@
+
+.ImageLayout {
+  width: 100%;
+  background-color: var(--color-neutral-10);
+
+  display: grid;
+  overflow: hidden;
+
+  & img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    object-position: center;
+  }
+
+  &[data-itemcount='5'], &[data-itemcount='4'] {
+    grid: 1fr 1fr / 2fr 1fr 1fr;
+
+    & img:nth-child(1) {
+      grid-row: 1 / span 2;
+    }
+  }
+
+  &[data-itemcount='3'] {
+    grid: 1fr 1fr / 2fr 1fr;
+
+    & img:nth-child(1) {
+      grid-row: 1 / span 2;
+    }
+  }
+
+  &[data-itemcount='2'] {
+    grid-template-columns: 1fr 1fr;
+  }
+
+  &[data-itemcount='1'] {
+    height: 50vw;
+
+    & img {
+      height: 100%;
+    }
+  }
+}
diff --git a/src/shared/ui/ImageLayout/ImageLayout.tsx b/src/shared/ui/ImageLayout/ImageLayout.tsx
new file mode 100644
index 0000000..af3464c
--- /dev/null
+++ b/src/shared/ui/ImageLayout/ImageLayout.tsx
@@ -0,0 +1,11 @@
+import styles from './ImageLayout.module.css';
+
+export const ImageLayout = ({ urls }: { urls: string[] }) => {
+  return (
+    <div className={styles.ImageLayout} data-itemcount={Math.min(urls.length, 5)}>
+      {urls.map((url) => (
+        <img key={url} src={url} alt={'프로필 이미지'} />
+      ))}
+    </div>
+  );
+};