3 Commity 1bf1a679e3 ... aa86f0802e

Autor SHA1 Wiadomość Data
  tuyetnhi aa86f0802e rank-page 1 rok temu
  tuyetnhi 2d2df95621 sales-raking-scroll-home 1 rok temu
  tuyetnhi fa34a3f335 artist-scroll-home 1 rok temu

+ 3 - 0
package.json

@@ -88,5 +88,8 @@
     "webpack-dev-server": "^3.10.3",
     "webpack-merge": "^4.2.2",
     "yarn": "^1.22.17"
+  },
+  "dependencies": {
+    "currency.js": "^2.0.4"
   }
 }

+ 13 - 0
src/@types/index.d.ts

@@ -302,6 +302,19 @@ declare module 'metarare' {
         created_at: string;
         is_official: boolean;
     }
+
+    export interface IArtist {
+        id: number;
+        name: string;
+        thumbnail_image: string;
+    }
+
+    export interface ISalesRanking {
+        id: number;
+        name: string;
+        thumbnail_image: string;
+        total_revenue: number;
+    }
 }
 
 declare module '*.svg';

+ 41 - 0
src/components/common/ArtistCard.scss

@@ -0,0 +1,41 @@
+@import "/assets/css/_lib";
+
+.artist_card {
+    position: relative;
+
+    &.scroll {
+        position: relative;
+        display: inline-block;
+        padding: 0 10px;
+        width: calc(50vw - 30px);
+        vertical-align: top;
+
+        @include pc {
+            width: 14%;
+            box-sizing: border-box;
+        }
+    }
+
+}
+
+.card_wrap {
+    position: relative;
+    overflow: hidden;
+    padding-bottom: 40px;
+    border-radius: 16px;
+    background-color: #fff;
+    box-sizing: border-box;
+    & > .thumb_area {
+        padding: 14%;
+        & > .thumb {
+            border-radius: 50%;
+            width: 100%;
+            aspect-ratio: 1;
+            height: auto;
+        }
+    }
+    & > .info_area{
+        text-align: center;
+        font-size: large;
+    }
+}

+ 37 - 0
src/components/common/ArtistCard.tsx

@@ -0,0 +1,37 @@
+import * as React from 'react';
+import styles from './ArtistCard.scss';
+import classNames from 'classnames/bind';
+import { Link } from 'react-router-dom';
+import Img from './Img';
+import { IArtist } from 'metarare';
+
+const cx = classNames.bind(styles);
+
+export interface IArtistCardProps extends IArtist {
+  className: string;
+}
+
+export default function ArtistCard ({
+  id,
+  name,
+  thumbnail_image,
+  className
+}: IArtistCardProps) {
+  return (
+    <div className={cx("artist_card", className )}>
+      <div className={cx("card_wrap")}>
+          <div className={cx("thumb_area")}>
+              <Img
+                src={thumbnail_image}
+                alt=""
+                width="260"
+                height="260"
+                className={cx("thumb")}
+              />
+           
+          </div>
+          <div className={cx("info_area")}>{name}</div>
+      </div>
+    </div>
+  );
+}

+ 60 - 0
src/components/common/SaleRankingCard.tsx

@@ -0,0 +1,60 @@
+import classNames from "classnames/bind";
+import { ISalesRanking } from "metarare";
+import * as React from "react";
+import Img from "./Img";
+import styles from "./SalesRankingCard.scss";
+import currency from "currency.js";
+
+const cx = classNames.bind(styles);
+
+export interface ISalesRankingCardProps extends ISalesRanking {
+  className: string;
+}
+
+export default function SalesRankingCard({
+  id: rank,
+  name,
+  thumbnail_image,
+  className,
+  total_revenue,
+}: ISalesRankingCardProps) {
+  const renderColor = () => {
+    if (rank === 1) return "#ffd700";
+    if (rank === 2) return "#c0c0c0";
+    if (rank === 3) return "#cd7f32";
+    return "#3d3d3d";
+  };
+  return (
+    <div className={cx("sales_ranking_card", className)}>
+      <div
+        className={cx("ranking")}
+        style={{
+          background: renderColor(),
+        }}
+      >
+        <span>{rank}</span>
+      </div>
+      <div className={cx("card_wrap")}>
+        <div className={cx("thumb_area")}>
+          <Img
+            src={thumbnail_image}
+            alt=""
+            width="260"
+            height="260"
+            className={cx("thumb")}
+          />
+        </div>
+        <div className={cx("info_area")}>
+          <div className={cx("total")}>
+            Total:{" "}
+            {currency(total_revenue).format({
+              symbol: "$",
+              precision: 0,
+            })}
+          </div>
+          <div className={cx("name")}>{name}</div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 68 - 0
src/components/common/SalesRankingCard.scss

@@ -0,0 +1,68 @@
+@import "/assets/css/_lib";
+
+.sales_ranking_card {
+    position: relative;
+
+    &.scroll {
+        position: relative;
+        display: inline-block;
+        padding: 0 10px;
+        width: calc(50vw - 30px);
+        vertical-align: top;
+
+        @include pc {
+            width: 14%;
+            box-sizing: border-box;
+        }
+    }
+    & > .ranking{
+        position: absolute;
+        top: 10%;
+        left: 20%;
+        width: 12%;
+        aspect-ratio: 1;
+        border-radius: 50%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 14px;
+        color: #fff;
+        z-index: 1;
+        font-family: 'HelveticaNeue', 'Helvetica', arial, 'sans-serif' !important;
+    }
+}
+
+.card_wrap {
+    position: relative;
+    overflow: hidden;
+    padding-bottom: 40px;
+    border-radius: 16px;
+    background-color: #fff;
+    box-sizing: border-box;
+
+    &>.thumb_area {
+        padding: 14%;
+
+        &>.thumb {
+            border-radius: 50%;
+            width: 100%;
+            aspect-ratio: 1;
+            height: auto;
+        }
+    }
+
+    &>.info_area {
+        text-align: center;
+        font-size: large;
+
+        &>.name {}
+
+        &>.total {
+            font-size: 14px;
+            font-weight: 400;
+            line-height: 20px;
+            color: #888;
+            cursor: pointer;
+        }
+    }
+}

+ 34 - 0
src/components/mobile/ArtistScrollList.tsx

@@ -0,0 +1,34 @@
+import React from 'react';
+import classNames from 'classnames/bind';
+
+import Flicking from '@egjs/react-flicking';
+import styles from './NFTScrollList.scss';
+import { IArtist, INFT } from 'metarare';
+import ArtistCard from '../common/ArtistCard';
+
+const cx = classNames.bind(styles);
+
+interface IOwnProps {
+  list: IArtist[];
+};
+
+const ArtistScrollList: React.FC<IOwnProps> = ({
+  list
+}) => {
+  return (
+    <div className={cx('nft_scroll')}>
+      <Flicking align="prev">
+        {list.map((item, index) => (
+          <div
+            key={`${item.id}_${index}`}
+            className={cx('nft_panel')}
+          >
+            <ArtistCard {...item} className='scroll' />
+          </div>
+        ))}
+      </Flicking>
+    </div>
+  );
+};
+
+export default ArtistScrollList;

+ 29 - 0
src/components/mobile/SalesRankingScrollList.tsx

@@ -0,0 +1,29 @@
+import React from "react";
+import classNames from "classnames/bind";
+
+import Flicking from "@egjs/react-flicking";
+import styles from "./NFTScrollList.scss";
+import { ISalesRanking, INFT } from "metarare";
+import SalesRankingCard from "../common/SaleRankingCard";
+
+const cx = classNames.bind(styles);
+
+interface IOwnProps {
+  list: ISalesRanking[];
+}
+
+const SalesRankingScrollList: React.FC<IOwnProps> = ({ list }) => {
+  return (
+    <div className={cx("nft_scroll")}>
+      <Flicking align="prev">
+        {list.map((item, index) => (
+          <div key={`${item.id}_${index}`} className={cx("nft_panel")}>
+            <SalesRankingCard {...item} className="scroll" />
+          </div>
+        ))}
+      </Flicking>
+    </div>
+  );
+};
+
+export default SalesRankingScrollList;

+ 59 - 0
src/components/pc/ArtistScrollList.tsx

@@ -0,0 +1,59 @@
+import React, { useEffect, useState } from 'react';
+import classNames from 'classnames/bind';
+
+import Flicking, { ViewportSlot } from '@egjs/react-flicking';
+import { Arrow } from '@egjs/flicking-plugins';
+import styles from './NFTScrollList.scss';
+import NFTCard from '@src/components/common/NFTCard';
+import { IArtist, INFT } from 'metarare';
+import ArtistCard from '../common/ArtistCard';
+
+const cx = classNames.bind(styles);
+
+interface IOwnProps {
+  list: IArtist[];
+};
+
+const PANEL_COUNT = 7;
+
+const ArtistScrollList: React.FC<IOwnProps> = ({
+  list
+}) => {
+  const plugins = [new Arrow()];
+  const [panel, setPanel] = useState<IArtist[][]>([]);
+
+  useEffect(() => {
+    if (list?.length) {
+      const panel = [];
+      for (let i = 0; i < list.length; i += PANEL_COUNT) {
+        panel.push(list.slice(i, i + PANEL_COUNT));
+      }
+
+      setPanel(panel);
+    }
+  }, [list]);
+
+  return (
+    <div className={cx('nft_scroll')}>
+      {!!panel.length && (
+        <Flicking plugins={plugins}>
+          {panel?.map((list, index) => (
+            <div className={cx('nft_panel')} key={index}>
+              {list.map((item, index) => (
+                <ArtistCard {...item} key={index} className='scroll' />
+              ))}
+            </div>
+          ))}
+          <ViewportSlot>
+            <div className={cx('pc_wrapper')}>
+              <span className={cx('flicking-arrow-prev', 'btn_flicking', 'main_prev')}></span>
+              <span className={cx('flicking-arrow-next', 'btn_flicking', 'main_next')}></span>
+            </div>
+          </ViewportSlot>
+        </Flicking>
+      )}
+    </div>
+  );
+};
+
+export default ArtistScrollList;

+ 0 - 1
src/components/pc/Header.tsx

@@ -8,7 +8,6 @@ import { ReactComponent as IconArrow } from "@assets/img/svg/ico_arr_gray.svg";
 import { ReactComponent as IconActivity } from "@assets/img/svg/ico_activity.svg";
 import { ReactComponent as IconTwitter } from "@assets/img/svg/ico_twitter.svg";
 import { ReactComponent as IconInstagram } from "@assets/img/svg/ico_insta.svg";
-import { ReactComponent as IconDiscord } from "@assets/img/svg/ico_discord.svg";
 import { ReactComponent as IconFacebook } from "@assets/img/svg/ico_facebook.svg";
 import URLInfo from "@src/constants/URLInfo";
 import useSearch from "@src/hooks/useSearch";

+ 74 - 0
src/components/pc/SalesRankingScrollList.tsx

@@ -0,0 +1,74 @@
+import React, { useEffect, useState } from 'react';
+import classNames from 'classnames/bind';
+
+import Flicking, { ViewportSlot } from '@egjs/react-flicking';
+import { Arrow } from '@egjs/flicking-plugins';
+import styles from './NFTScrollList.scss';
+import { ISalesRanking, INFT } from 'metarare';
+import SalesRankingCard from '../common/SaleRankingCard';
+
+const cx = classNames.bind(styles);
+
+interface IOwnProps {
+  list: ISalesRanking[];
+};
+
+const PANEL_COUNT = 7;
+
+const SalesRankingScrollList: React.FC<IOwnProps> = ({
+  list
+}) => {
+  const plugins = [new Arrow()];
+  const [panel, setPanel] = useState<ISalesRanking[][]>([]);
+
+  useEffect(() => {
+    if (list?.length) {
+      const panel = [];
+      for (let i = 0; i < list.length; i += PANEL_COUNT) {
+        panel.push(list.slice(i, i + PANEL_COUNT));
+      }
+
+      setPanel(panel);
+    }
+  }, [list]);
+
+  return (
+    <div className={cx("nft_scroll")}>
+      {!!panel.length && (
+        <Flicking plugins={plugins}>
+          {panel?.map((list, index) => (
+            <div className={cx("nft_panel")} key={index}>
+              {list.map((item, index) => (
+                <SalesRankingCard
+                  {...item}
+                  key={index}
+                  className="scroll"
+                />
+              ))}
+            </div>
+          ))}
+          <ViewportSlot>
+            <div className={cx("pc_wrapper")}>
+              <span
+                className={cx(
+                  "flicking-arrow-prev",
+                  "btn_flicking",
+                  "main_prev"
+                )}
+              ></span>
+              <span
+                className={cx(
+                  "flicking-arrow-next",
+                  "btn_flicking",
+                  "main_next"
+                )}
+              ></span>
+            </div>
+          </ViewportSlot>
+        </Flicking>
+      )}
+    </div>
+  );
+};
+
+export default SalesRankingScrollList;

+ 1 - 1
src/constants/URLInfo.ts

@@ -19,7 +19,7 @@ const URL = {
   GUIDE_DETAIL: '/guide/:id',
   LOGIN_CALLBACK: '/callback/:type',
   ERROR: '/error',
-  RANKING: '',
+  RANKING: '/ranking',
 };
 
 const SEARCH_PARAM = {

+ 73 - 0
src/containers/common/ArtistsScrollList.tsx

@@ -0,0 +1,73 @@
+import { IArtist } from 'metarare';
+import * as React from 'react';
+import PCArtistScrollList from "@src/components/pc/ArtistScrollList";
+import MobileArtistScrollList from "@src/components/mobile/ArtistScrollList";
+
+export interface IArtistsScrollListProps {}
+
+export default function ArtistsScrollList(props: IArtistsScrollListProps) {
+  return (
+    <>
+      <div className={"mo_wrapper"}>
+        <MobileArtistScrollList list={dataFake} />
+      </div>
+      <div className={"pc_wrapper"}>
+        <PCArtistScrollList list={dataFake} />
+      </div>
+    </>
+  );
+}
+
+
+const dataFake: IArtist[] = [
+    {
+        id: 1,
+        name: 'John Doe',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 2,
+        name: 'Kasper Henderson',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 3,
+        name: 'Cooper Lynn',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 4,
+        name: 'Iris Stein',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 5,
+        name: 'Leland Richards',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 6,
+        name: 'Octavia Norris',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 7,
+        name: 'Leland Richards',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 8,
+        name: 'Octavia Norris',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 9,
+        name: 'Leland Richards',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    },
+    {
+        id: 10,
+        name: 'Octavia Norris',
+        thumbnail_image: 'https://picsum.photos/300/300',
+    }
+]

+ 82 - 0
src/containers/common/SalesRankingscrollList.tsx

@@ -0,0 +1,82 @@
+import MobileSalesRankingScrollList from "@src/components/mobile/SalesRankingScrollList";
+import PCSalesRankingScrollList from "@src/components/pc/SalesRankingScrollList";
+import { ISalesRanking } from "metarare";
+import * as React from "react";
+
+export interface ISalesRankingScrollListProps {}
+
+export default function SalesRankingScrollList(props: ISalesRankingScrollListProps) {
+  return (
+    <>
+      <div className={"mo_wrapper"}>
+        <MobileSalesRankingScrollList list={dataFake} />
+      </div>
+      <div className={"pc_wrapper"}>
+        <PCSalesRankingScrollList list={dataFake} />
+      </div>
+    </>
+  );
+}
+
+const dataFake: ISalesRanking[] = [
+  {
+    id: 1,
+    name: "John Doe",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 100000,
+  },
+  {
+    id: 2,
+    name: "Kasper Henderson",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 90000,
+  },
+  {
+    id: 3,
+    name: "Cooper Lynn",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 80000,
+  },
+  {
+    id: 4,
+    name: "Iris Stein",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 70000,
+  },
+  {
+    id: 5,
+    name: "Leland Richards",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 60000,
+  },
+  {
+    id: 6,
+    name: "Octavia Norris",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 50000,
+  },
+  {
+    id: 7,
+    name: "Leland Richards",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 40000,
+  },
+  {
+    id: 8,
+    name: "Octavia Norris",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 30000,
+  },
+  {
+    id: 9,
+    name: "Leland Richards",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 20000,
+  },
+  {
+    id: 10,
+    name: "Octavia Norris",
+    thumbnail_image: "https://picsum.photos/300/300",
+    total_revenue: 10000,
+  },
+];

+ 9 - 0
src/pages/Home.scss

@@ -23,4 +23,13 @@
           line-height: 32px;
       }
   }
+}
+
+.see_more{
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 20px;
+    color: #005abc;
+    cursor: pointer;
+    margin-left: 20px;
 }

+ 11 - 2
src/pages/Home.tsx

@@ -10,8 +10,9 @@ import { useDispatch, useSelector } from 'react-redux';
 import { fetchExploreHome, resetExploreHome } from '@src/store/reducers/ExploreReducer';
 import { fetchHome } from '@src/store/reducers/HomeReducer';
 import Loading from '@src/components/common/Loading';
-import ExploreContentContainer from '@src/containers/common/ExploreContentContainer';
 import useMore from '@src/hooks/useMore';
+import ArtistsScrollList from '@src/containers/common/ArtistsScrollList';
+import SalesRankingScrollList from '@src/containers/common/SalesRankingscrollList';
 
 const cx = classNames.bind(styles);
 
@@ -87,9 +88,17 @@ const Home: React.FC = () => {
               <CollectionScrollList list={data.collections} />
             </div>
           )}
-          <div className={cx('home_group')}>
+          {/* <div className={cx('home_group')}>
             <strong className={cx('home_title')}>탐색하기 🔍</strong>
             <ExploreContentContainer list={filterProps.exploreList} filterProps={filterProps} handler={filterHandler} onClickMore={onClickMore} showMore={showMore} isLoaded={isExploreListLoaded} />
+          </div> */}
+          <div className={cx('home_group')}>
+            <strong className={cx('home_title')}>Registered Artists 🧑‍🎨</strong>
+            <ArtistsScrollList />
+          </div>
+          <div className={cx('home_group')}>
+            <strong className={cx('home_title')}>Sales Ranking 🏆 <a className={cx('see_more')}>Sea more</a></strong>
+            <SalesRankingScrollList />
           </div>
         </>
       )}

+ 20 - 0
src/pages/Ranking.scss

@@ -0,0 +1,20 @@
+@import "/assets/css/_lib";
+
+.ranking {
+  padding: 72px 16px 390px;
+  min-height: 100%;
+  margin: 0 auto -327px;
+  box-sizing: border-box;
+  @include pc {
+    width: 696px;
+    padding: 80px 0 290px;
+    margin-bottom: -210px;
+  }
+  .title {
+    display: block;
+    margin-top: 30px;
+    font-size: 24px;
+    line-height: 25px;
+    font-weight: normal;
+  }
+}

+ 17 - 0
src/pages/Ranking.tsx

@@ -0,0 +1,17 @@
+import * as React from 'react';
+import classNames from 'classnames/bind';
+import styles from './Ranking.scss';
+
+const cx = classNames.bind(styles);
+
+export interface IRankingProps {
+}
+
+export default function Ranking (props: IRankingProps) {
+  return (
+    <div className={cx('ranking')}>
+        <div className={cx('title')}>랭킹</div>
+        
+    </div>
+  );
+}

+ 6 - 0
src/routes.ts

@@ -80,4 +80,10 @@ export const routes = [
     exact: true,
     component: lazy(() => import('@src/pages/Error')),
   },
+  {
+    path: URLInfo.RANKING,
+    title: '랭킹',
+    exact: true,
+    component: lazy(() => import('@src/pages/Ranking')),
+  }
 ];

+ 5 - 0
yarn.lock

@@ -4172,6 +4172,11 @@ csstype@^3.0.2:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
   integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
 
+currency.js@^2.0.4:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/currency.js/-/currency.js-2.0.4.tgz#a8a4d69be3b2e509bf67a560c78220bc04809cf1"
+  integrity sha512-6/OplJYgJ0RUlli74d93HJ/OsKVBi8lB1+Z6eJYS1YZzBuIp4qKKHpJ7ad+GvTlWmLR/hLJOWTykN5Nm8NJ7+w==
+
 cyclist@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"