Skip to content
Open

, #1

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions client/components/BrandFocus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import Link from "next/link";
import React from "react";

function BrandFocus() {
return (
<div className="w-[1280px] h-[479px] mt-[8rem] mx-auto px-10">
<div className="flex-row">
<div className="text-[20px] font-semibold tracking-tighter">
Brand Focus
</div>
<div className="text-[12px] text-gray-400">추천 브랜드</div>
<div className="h-[416px] mt-[1rem] grid grid-cols-5">
{/* map 함수가 return값이 필요해서 return 해와야 된다라고 오인 */}
{BrandArray1.map((item, i) => (
<div key={i}>
<Link href={`/brands/${i}`}>
<img
src={item?.ImageUrl}
className="w-[228px] ml-[4px] mr-[4px]"
/>
</Link>
<div className="mt-[8px] mb-[8px] mr-[8px] text-[15px] flex justify-center">
{item?.name}
</div>
</div>
))}
</div>
</div>
</div>
);
}

export default BrandFocus;

interface Brand {
name: string;
ImageUrl: string;
}

const BrandArray1: Array<Brand | undefined> = [
{
name: "애플",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfNTMg/MDAxNjYxMTMyMzQ4Njg5.RsvtKwAj6FHj1FDDL5TY1ycECnCPB12EttYPY5M1gBsg.vpRsBYsFuP-GSd4ODRJe2P4TkiVFE9Buu6Yj777Kesog.JPEG/a_d8ca5e787c1a47f086c405177abc9694.jpg?type=m_webp",
},
{
name: "루이비통",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA5MzBfMTM2/MDAxNjY0NTM2NDQ2MzQ5.kC1qMK6mltl0PvSNqWiq3KroRn067Mbae-OzlEnwMEQg.jcAazVYaMq-ZB6fewYkisfqBQIIK6n6L59h_1PpKwaUg.JPEG/a_8a407a9e001a425ab4c10864c6fd273d.jpg?type=m_webp",
},
{
name: "강혁",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjEwMDdfMTQ2/MDAxNjY1MTM0ODc5Mzcx.hWLeBlp0yyEWGq6goJeMLPoSZTTgufx7_2wLDPvOLeYg.xycA1YUscG0NS63oPgVIpxoMhLt_2z-n_Xz4zqApRE8g.JPEG/a_e9f65e8566144b8e8aaf25ab788969dd.jpg?type=m_webp",
},
{
name: "우영미",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfMjUy/MDAxNjYxMTMyNTE3NDkw.WBGPz7YTPljlTjBVVGNKHY8a0UohliA2ahbwJmqF4rAg.4QYXLQna4D-CHul0MSeru4vP1dcn-qRVkPDuezT3_5sg.JPEG/a_5f636bae775f4b5f8538d969e6916238.jpg?type=m_webp",
},
{
name: "에르메스",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfMTU2/MDAxNjYxMTMyMjc3ODYy.o_bZ8NwB1fDxBycvBadiwSjJ3sJrAfNcgzHXZO3SCskg.U9-45gdxEevSSwnM8xAHqDBdIsBhbR4TCwK3xrBmMaIg.JPEG/a_41ee4f97c403419784fe145cbfe9e950.jpg?type=m_webp",
},
{
name: "발렌시아가",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfMjgw/MDAxNjYxMTMyNTAwODI0.uvibwIpVXKhBYiNd5gCCRH-W0qk-RX3J1FXF58o69nMg.aa6DHYk0_42QwB5scZ2taZL8WGKmfkcMlOMWl9AV5z0g.JPEG/a_4ed967e942554a14a5687b0d527bbe58.jpg?type=m_webp",
},
{
name: "아크테릭스",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA5MzBfMjMz/MDAxNjY0NTM2NzM2Mzc1.6w2bcZkBccLv112TzFBOvWKzBsx8qE4A4Q4ODq5uz6Ig.qOOuTt5_Ch5khC3ODXUGnh1s1ErTwxg42w4fG2lzzDAg.JPEG/a_54af49e527524cdc98db28d6fb1ffade.jpg?type=m_webp",
},
{
name: "렉토",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjEwMDdfMTEx/MDAxNjY1MTM0ODM4MDMw.HzVvSOi98fjQmP3oroAmNe2sR32FZDFbobzy7tjpIZ4g.reteIbS3HkHf-UxIRYF-BIHFxCpH1JrRcEFSmcJVxfMg.JPEG/a_02632df7a3a049e7b6037802a5a206b4.jpg?type=m_webp",
},
{
name: "샤넬",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfODMg/MDAxNjYxMTMyMjY2NDU0.veNBMpvgteBr5I5SLuqNPHR8CkwWhEytMWZoG735frkg.PerHHfgDwBfbVU1BIwEXChh2_-w8u261iDgwUJiCp54g.JPEG/a_e37129ba9c134102b8f886ee9cfc5b57.jpg?type=m_webp",
},
{
name: "디올",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfMjM2/MDAxNjYxMTMyMzE0NTg5.f8aopPNBfz2TiMXJPHCA4cDSE564M-5m_1CVOqBaMJcg.LP8fIRuj3KwekpzWqPNM1E7udU5MgzCeyeVG5hL8oFog.JPEG/a_dd2fc85edf3b45e18d1c1b83180c004c.jpg?type=m_webp",
},
{
name: "스톤 아일랜드",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfMTgg/MDAxNjYxMTMyNDg5MzI4.91XVGjgCI_MCIl_wqButgXZUy7kdk_yXjOn2kg8y6AEg.dyIleHyozgiFkXvyk8pAn0SUAuz2IkwxqXxVC1Clb48g.JPEG/a_36de4c2d690d423ea5ba7a876b00ebd3.jpg?type=m_webp",
},
{
name: "롤렉스",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfMTgy/MDAxNjYxMTMyMzI1ODY2.omWU_zpi2-_4IK9XyTPQFDfqADR6p8yqhK_VdchChFwg.h2JuTtFOH763vLDGZIbMj2HC53joOaME44CbdEjGaA0g.JPEG/a_080b41c445b94290b693aa53a8751fc5.jpg?type=m_webp",
},
,
{
name: "보테가 베네타",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfMTY2/MDAxNjYxMTMyNDE3MDg1.NH05zJR5hBf3wHvQNhnvKLVxlGLtXBwaLYNuxsXQL78g.B2wHGjWAx_2n6oXfK4gy92vHpu7hbjin0U_XNwa8mlcg.JPEG/a_fe6d110270b4422e9d35b90e66a3235d.jpg?type=m_webp",
},
,
{
name: "셀린느",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfMTEw/MDAxNjYxMTMyNDI3Njg4.gNnZj-Nb_7BNlNaJ0KMwEoxtp1E3O_PYvNFiwz0USPEg.cVgu4uf4pB24POhPepRYihXTIG146a9_iShSKfCPFnsg.JPEG/a_ecbebc5f0f354369bac78d86766ed990.jpg?type=m_webp",
},
,
{
name: "프라다",
ImageUrl:
"https://kream-phinf.pstatic.net/MjAyMjA4MjJfNTAg/MDAxNjYxMTMyNDY2ODM1.IYpY8jJHjZTN9g-Yjc9HJ3x7npeZ-0VCrMr4KApSSNMg.a_Wtikd-hRYxM1nImn9s94MVpuzoLz04JPpGOvQM52Mg.JPEG/a_1d7ab41649cc478988f6759f8d6c2bc0.jpg?type=m_webp",
},
];
117 changes: 117 additions & 0 deletions client/components/Carousel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import icons from "./icons";
import { useState, useEffect, useCallback, MouseEvent } from "react";
import { cls, cycler } from "../utils";
import { Picture } from "../types";

interface Props {
pictures: Picture[];
}

const Carousel = ({ pictures }: Props) => {
const [selected, setSelected] = useState(0);
const [dragState, setDragState] = useState({
startPoint: 0,
isDragging: false,
isFlipped: false,
});
const pictureCycler = cycler(pictures.length);
const handleChangeLeft = () => {
setSelected((cur) => pictureCycler(cur - 1));
};
const handleChangeRight = () => {
setSelected((cur) => pictureCycler(cur + 1));
};
const handleClickPicture =
(selected: number) => (event: MouseEvent<HTMLElement>) => {
console.log(selected);
console.log("event.target : ", event);
};
const nextImage = useCallback(() => {
setSelected((cur) => pictureCycler(cur + 1));
}, []);
useEffect(() => {
setInterval(nextImage, 5000);
}, []);
const handleDrag = (e: MouseEvent<HTMLElement>) => {
if (dragState.isFlipped) return;
if (e.nativeEvent.screenX - dragState.startPoint < -50) {
setSelected((cur) => pictureCycler(cur + 1));
setDragState((cur) => ({
...cur,
isFlipped: true,
}));
} else if (e.nativeEvent.screenX - dragState.startPoint > 50) {
setSelected((cur) => pictureCycler(cur - 1));
setDragState((cur) => ({
...cur,
isFlipped: true,
}));
}
};
const handleDragStart = (e: MouseEvent<HTMLElement>) => {
setDragState(() => ({
isFlipped: false,
isDragging: true,
startPoint: e.nativeEvent.screenX,
}));
};
const handleDragEnd = (e: MouseEvent<HTMLElement>) => {
setDragState((cur) => ({
...cur,
isDragging: false,
}));
};
const handleClickIndicator = (index: number) => () => {
setSelected(index);
};

return (
<div
onClick={handleClickPicture(selected)}
className={cls(
"relative w-full min-h-[480px]",
dragState.isDragging ? "cursor-grabbing" : "cursor-pointer" //질문하기 cusor-grabbing 적용하려면?
)}
onDrag={handleDrag}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
{pictures.map((picture, i) => (
<img
key={picture.urls}
src={picture.urls}
className={cls(
"absolute w-full h-full duration-200 ease-linear transition-all object-contain",
picture.color,
i > selected ? "opacity-0" : "opacity-100 "
)}
/>
))}

<div className="absolute w-full flex justify-between top-1/2">
<icons.LeftChevron
className="text-white w-20 h-20 flex justify-center items-center cursor-pointer"
onClick={handleChangeLeft}
/>
<icons.RightChevron
className="text-white w-20 h-20 flex justify-center items-center cursor-pointer"
onClick={handleChangeRight}
/>
</div>
<ul className="absolute w-full flex justify-center gap-3 bottom-8">
{pictures.map((picture, i) => (
<li
key={picture.urls}
className={cls(
"w-2 h-2 rounded-full duration-200 ease-linear transition-all",
i === selected ? "bg-slate-600" : "bg-slate-200"
)}
onClick={handleClickIndicator(i)}
/>
))}
</ul>
</div>
);
};

export default Carousel;
63 changes: 63 additions & 0 deletions client/components/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import icons from "./icons";
import { MouseEventHandler } from "react";
import { cls } from "../utils";
import { UseFormRegister, FieldValues } from "react-hook-form";
interface Props {
main?: boolean;
plus?: boolean;
minus?: boolean;
sub?: boolean;
detail?: boolean;
body: string;
className?: string;
onClickSecond?: MouseEventHandler;
}

const Checkbox = ({
main,
plus,
minus,
sub,
detail,
body,
className,
onClickSecond,
}: Props) => {
return (
<div
className={cls("flex justify-between text-sm items-center", className)}
>
<label className="relative flex items-center cursor-pointer">
<input
className="w=[0.9px] h-[0.9px] overflow-hidden absolute clip-0 peer"
type="checkbox"
></input>
<icons.Check
className="w-6 h-6 border peer-checked:bg-black flex justify-center items-center"
iconClassName="text-white w-4 h-4"
strokeWidth={2.5}
/>
<span className="pl-2">{body}</span>
</label>
{main && plus ? (
<icons.Plus
className="ml-4 w-6 h-6 cursor-pointer"
onClick={onClickSecond}
/>
) : null}
{main && minus ? (
<icons.Minus
className="ml-4 w-6 h-6 cursor-pointer"
onClick={onClickSecond}
/>
) : null}
{sub && detail ? (
<a className="h-5 text-gray-400 underline text-xs cursor-pointer">
내용 보기
</a>
) : null}
</div>
);
};

export default Checkbox;
23 changes: 23 additions & 0 deletions client/components/Infor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useRouter } from "next/router";

interface InforProps {
name: string;
url: string;
}

const Infor = ({ name, url }: InforProps) => {
const router = useRouter();
const handleClick = () => {
router.push(url);
};
return (
<li
onClick={handleClick}
className="cursor-pointer motion-reduce:hidden first:mt-0 mt-3 text-gray-400 text-sm"
>
{name}
</li>
);
};

export default Infor;
51 changes: 51 additions & 0 deletions client/components/ItemCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Item } from "../types";
import { attachComma, cls } from "../utils";

interface Props {
item: Item;
}
const ItemCard = ({ item }: Props) => {
return (
<div className="w-72 flex-1 my-5 px-3">
<div className="w-full h-auto">
<div className={cls("h-[282px] relative rounded-xl", item.color)}>
<img className="absolute" src={item.imgUrl} alt="" />
<a
className="top-2 right-2 absolute w-8 h-8 flex justify-center items-center text-gray-800"
href="#"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0111.186 0z" />
</svg>
</a>
</div>
<div className="pt-[9px] text-gray-800">
<div className="leading-3 text-sm pb-0.5 border-b border-b-black inline-block font-bold">
{item.brand}
</div>
<div className="text-sm mt-[8.5px] mb-0.5">{item.name}</div>
{item.isFast ? (
<div className="text-[11px] relative before:top-[4px] before:left-[3.5px] before:content-[''] before:absolute before:block before:h-[15px] before:w-[11px] before:bg-[url(https://kream.co.kr/_nuxt/img/ico-express.8dac9dc.svg)] text-green-400 py-[4.5px] pr-[5.5px] pl-[17px] inline-block">
빠른배송
</div>
) : null}
<div className="pt-[7px]">
<div className="text-[15px] font-bold">
{attachComma(item.price)}원
</div>
<div className="text-[11px] text-gray-400">즉시 구매가</div>
</div>
</div>
</div>
</div>
);
};

export default ItemCard;
11 changes: 11 additions & 0 deletions client/components/Portal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ReactDOM from "react-dom";
import { ReactNode } from "react";
interface Props {
children: ReactNode;
}
const Portal = ({ children }: Props) => {
const modalElement = document.querySelector("#portal") as HTMLElement;
return ReactDOM.createPortal(children, modalElement);
};

export default Portal;
Loading