Refactoring
이제 중복되는 코드들을 싹다 묶어보는 깔끔하게!! 묶어봅시다~ 먼저 Home Screen부분입니다.
/pages/index.tsx
import type { NextPage } from "next";
import Layout from "../components/layout";
import Item from "../components/item";
import FloatingButton from "../components/FloatingButton";
const Home: NextPage = () => {
return (
<Layout title="홈" hasTabBar>
<div className="p flex flex-col space-y-5 py-2">
{[...Array(11)].map((_, i) => (
<Item
key={i}
id={i + 1}
title="new iPhone 14"
price={95}
comments={1}
hearts={1}
/>
))}
<FloatingButton href="/items/upload">
<svg
className="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
</FloatingButton>
</div>
</Layout>
);
};
export default Home;
/components/item.tsx
import Link from "next/link";
interface ItemProps {
title: string;
id: number;
price: number;
comments: number;
hearts: number;
}
const Item = ({ title, price, id, comments, hearts }: ItemProps) => {
return (
<Link href={`/items/${id}`}>
<a className="flex cursor-pointer justify-between border-b px-4 pb-4">
<div className="flex space-x-4">
<div className="h-20 w-20 rounded-md bg-gray-400" />
<div className="flex flex-col pt-2">
<h3 className="text-sm font-medium text-gray-900">{title}</h3>
<span className="font-md mt-1 text-gray-900">${price}</span>
</div>
</div>
<div className="flex items-end justify-end space-x-2">
<div className="flex items-center space-x-0.5 text-sm text-gray-600">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
></path>
</svg>
<span>{hearts}</span>
</div>
<div className="flex items-center space-x-0.5 text-sm text-gray-600">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
></path>
</svg>
<span>{comments}</span>
</div>
</div>
</a>
</Link>
);
};
export default Item;
/components/FloatingButton.tsx
import Link from "next/link";
import React from "react";
interface FloatingButton {
children: React.ReactNode;
href: string;
}
const FloatingButton = ({ children, href }: FloatingButton) => {
return (
<Link href={href}>
<a className="fixed bottom-24 right-5 cursor-pointer rounded-full bg-orange-400 p-4 text-white shadow-xl transition-colors duration-300 hover:bg-orange-500">
{children}
</a>
</Link>
);
};
export default FloatingButton;
다음과 같이 Home Screen에서의 Item들과 부유버튼(FAB)을 Refactoring하였습니다. 속이 다 시원하네요~~
그다음은 Enter Page부분입니다.
/pages/enter.tsx
import { useState } from "react";
import Button from "../components/button";
import Input from "../components/Input";
import { cls } from "../libs/utils";
type MethodType = "email" | "phone";
export default function Enter() {
const [method, setMethod] = useState<MethodType>("email");
const onEmailClick = () => setMethod("email");
const onPhoneClick = () => setMethod("phone");
return (
<div className="mt-16 px-4">
<h3 className="text-center text-3xl font-bold">Enter to Carrot</h3>
<div className="mt-8">
<div className="flex flex-col items-center">
<h5 className="text-sm font-medium text-gray-500">Enter using:</h5>
<div className="mt-8 grid w-full grid-cols-2 gap-16 border-b">
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "email"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onEmailClick}
>
Email
</button>
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "phone"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onPhoneClick}
>
Phone
</button>
</div>
</div>
<form className="mt-8 flex flex-col">
{method === "email" ? (
<Input name="email" label="Email address" type="email" required />
) : null}
{method === "phone" ? (
<Input
name="phone"
label="Phone number"
type="number"
kind="phone"
required
/>
) : null}
{method === "email" ? <Button text={"Get login link"} /> : null}
{method === "phone" ? <Button text={"Get one-time password"} /> : null}
</form>
<div className="mt-8">
<div className="relative">
<div className="absolute w-full border-t border-gray-300" />
<div className="relative -top-3 text-center ">
<span className="bg-white px-2 text-sm text-gray-500">
Or enter with
</span>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3">
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M6.29 18.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0020 3.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.073 4.073 0 01.8 7.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 010 16.407a11.616 11.616 0 006.29 1.84" />
</svg>
</button>
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
);
}
/components/Input.tsx
interface InputProps {
label: string;
name: string;
kind?: "text" | "phone" | "price";
[key: string]: any;
}
/* defualt is kind="text" */
const Input = ({ name, label, kind = "text", rest }: InputProps) => {
return (
<div>
<label htmlFor={name} className="text-sm font-medium text-gray-700">
{label}
</label>
{kind == "text" ? (
<div className="mt-2 flex items-center shadow-sm">
<input
id={name}
{...rest}
className="w-full appearance-none rounded-md border border-gray-300
px-3 py-2 placeholder-gray-400 shadow-sm focus:border-orange-500
focus:outline-none focus:ring-orange-500"
/>
</div>
) : null}
{kind == "phone" ? (
<div className="mt-2 flex rounded-md shadow-sm">
<span className="flex select-none items-center justify-center rounded-l-md border border-r-0 border-gray-300 bg-gray-50 px-3 text-sm text-gray-500">
+82
</span>
<input
id="input"
type="number"
className="w-full appearance-none rounded-r-md border border-gray-300
px-3 py-2 placeholder-gray-400 shadow-sm focus:border-orange-500 focus:outline-none focus:ring-orange-500"
required
/>
</div>
) : null}
</div>
);
};
export default Input;
/components/Button.tsx
import { cls } from "../libs/utils";
interface ButtonProps {
large?: boolean;
text: string;
[key: string]: any;
}
const Button = ({ text, large, rest }: ButtonProps) => {
return (
<button
{...rest}
className={cls(
"mt-6 rounded-md border border-transparent bg-orange-500 px-4 font-medium text-white shadow-sm hover:bg-orange-600 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2",
large ? "py-3 text-base" : "py-2 text-sm"
)}
>
{text}
</button>
);
};
export default Button;
다음과 같이 Enter Page를 리팩토링 하였습니다. 크게 Input, Button을 따로 뺴놓게 되었습니다. ( 아직 Button component는 완성되지 않았습니다. )
다음은 community Page를 리팩토링 해보겠습니다. 앞서서 만든 component를 참고하거나 새로 component를 만들어서 리팩토링 해보겠습니다.
/pages/community/index.tsx
import type { NextPage } from "next";
import Link from "next/link";
import Layout from "../../components/layout";
import FloatingButton from "../../components/FloatingButton";
const Community: NextPage = () => {
return (
<Layout title="동네 생활" hasTabBar>
<div className="space-y-8 px-4 py-2">
{[...Array(10)].map((_, i) => (
<Link key={i} href={`/community/${i}`}>
<a className="flex cursor-pointer flex-col items-start pt-3">
<span className="flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800">
동네질문
</span>
<div className="mt-2 text-gray-700">
<span className="font-medium text-orange-500">Q.</span> What is the
best mandu restaurant?
</div>
<div className="mt-5 flex w-full items-center justify-between text-xs font-medium text-gray-500">
<span>현서</span>
<span>18시간 전</span>
</div>
<div className="mt-3 flex w-full space-x-5 border-t border-b-[2px] py-2.5 text-gray-700">
<div className="flex items-center space-x-2 text-sm">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<div>궁금해요 1</div>
</div>
<div className="flex items-center space-x-2 text-sm">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
></path>
</svg>
<div>답변 1</div>
</div>
</div>
</a>
</Link>
))}
<FloatingButton href="/community/write">
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
></path>
</svg>
</FloatingButton>
</div>
</Layout>
);
};
export default Community;
/pages/community/write.tsx
import type { NextPage } from "next";
import Layout from "../../components/layout";
import Button from "../../components/button";
import TextArea from "../../components/TextArea";
const Write: NextPage = () => {
return (
<Layout canGoBack>
<form className="space-y-2 px-4">
<div className="px-4">
<TextArea placeholder="Ask a question!" required />
<Button text="submit" />
</div>
</form>
</Layout>
);
};
export default Write;
여기에서 추가된 TextArea component를 만들겠습니다.
/components/textArea.tsx
interface TextAreaProps {
name?: string;
[key: string]: any;
}
const TextArea = ({ name, ...rest }: TextAreaProps) => {
return (
<textarea
id={name}
{...rest}
rows={4}
className="focus:border-1 mt-1 w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
/>
);
};
export default TextArea;
textArea는 여기서 완성된 것이 아닙니다. 조금 이따가 더 수정하도록 하겠습니다.
/pages/community/[id].tsx
import type { NextPage } from "next";
import Layout from "../../components/layout";
import TextArea from "../../components/TextArea";
import Button from "../../components/button";
const CommunityPostDetail: NextPage = () => {
return (
<Layout canGoBack>
<div>
<span className="my-3 inline-flex items-center rounded-full bg-gray-100 px-4 py-0.5 text-xs font-medium text-gray-800">
동네질문
</span>
<div className="mb-3 flex items-center space-x-3 border-b py-2 px-3">
<div className="h-10 w-10 rounded-full bg-slate-300" />
<div>
<p className="text-sm font-medium text-gray-700">Steve Jebs</p>
<p className="cursor-pointer text-xs font-medium text-gray-500">
View profile →
</p>
</div>
</div>
<div>
<div className="mt-2 px-4 text-gray-700">
<span className="font-medium text-orange-500">Q.</span> What is the best
mandu restaurant?
</div>
<div className="mt-3 flex w-full space-x-5 border-t border-b-[2px] py-2.5 px-4 text-gray-700">
<div className="flex items-center space-x-2 text-sm">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<div>궁금해요 1</div>
</div>
<div className="flex items-center space-x-2 text-sm">
<svg
className="h-4 w-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
></path>
</svg>
<div>답변 1</div>
</div>
</div>
</div>
<div className="my-5 space-y-5 px-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="flex items-start space-x-3">
<div className="h-8 w-8 rounded-full bg-slate-200" />
<div className="">
<span className="block text-sm font-medium text-gray-700">
Steve Jebs
</span>
<span className="block text-xs font-medium text-gray-500">
2시간 전
</span>
<p className="mt-[1.5px] text-gray-700">
The best mandu restaurant is the one next to my house.
</p>
</div>
</div>
))}
</div>
<div className="px-4">
<TextArea placeholder="Answer this question!" name="description" />
<Button text="Reply" />
</div>
</div>
</Layout>
);
};
export default CommunityPostDetail;
다음으로는 chats Page를 리팩토링 해보도록 하겠습니다.
/pages/chats/index.tsx
import type { NextPage } from "next";
import Layout from "../../components/layout";
import Link from "next/link";
const Write: NextPage = () => {
return (
<Layout title="채팅" hasTabBar>
<div className="divide-y-[1px] py-3">
{[...Array(15)].map((_, i) => (
<Link href={`/chats/${i}`} key={i}>
<a className="mb-3 flex items-center space-x-3 py-2 px-3 ">
<div className="h-12 w-12 rounded-full bg-slate-300" />
<div>
<p className="text-gray-700">Steve Jebs</p>
<p className="text-sm text-gray-500">
See you dtomorrow in the corner at 2pm
</p>
</div>
</a>
</Link>
))}
</div>
</Layout>
);
};
export default Write;
/pages/chats/[id].tsx
import type { NextPage } from "next";
import Layout from "../../components/layout";
import Message from "../../components/message";
const ChatDetail: NextPage = () => {
return (
<Layout title="채팅" hasTabBar>
<div className="space-y-4 py-3 px-4">
<Message message="Hi how much are you selling them for?" />
<Message message="I want ₩20,000" reversed />
<Message message="미쳤어" />
<Message message="미쳤어" />
<Message message="I want ₩20,000" reversed />
<Message message="I want ₩20,000" reversed />
<Message message="Hi how much are you selling them for?" />
<form className="fixed inset-x-0 bottom-20 bg-white py-2">
<div className="relative mx-auto flex w-full max-w-md items-center">
<input
type="text"
className="w-full rounded-full border-gray-300 pr-12 shadow-sm focus:border-orange-500 focus:outline-none focus:ring-orange-500"
/>
<div className="absolute inset-y-0 right-0 flex py-1.5 pr-1.5">
<button className="flex items-center rounded-full bg-orange-500 px-3 text-sm text-white hover:bg-orange-600 focus:ring-2 focus:ring-orange-500 focus:ring-offset-2">
→
</button>
</div>
</div>
</form>
</div>
</Layout>
);
};
export default ChatDetail;
/components/message.tsx
import { cls } from "../libs/utils";
interface MessageProps {
message: string;
reversed?: boolean;
avatarUrl?: string;
}
const Message = ({ avatarUrl, message, reversed }: MessageProps) => {
return (
<div
className={cls(
"flex items-start space-x-2",
reversed ? "flex-row-reverse space-x-reverse" : ""
)}
>
<div className="aspect-square h-8 rounded-full bg-slate-400" />
<div className="w-1/2 rounded-md border border-gray-300 p-2 text-sm text-gray-700">
<p>{message}</p>
</div>
</div>
);
};
export default Message;
'Web > CloneCoding' 카테고리의 다른 글
[Carrot Market] #6 Prisma - PlanetScale (0) | 2022.05.03 |
---|---|
[Carrot Market] #5 TAILWIND ReFactoring UI - 2 (0) | 2022.05.02 |
[Carrot Market] #5 TAILWIND CLONING UI - 3 (0) | 2022.05.01 |
[Carrot Market] #5 TAILWIND CLONING UI - 2 (0) | 2022.05.01 |
[Carrot Market] #5 TAILWIND CLONING UI - 1 (0) | 2022.05.01 |