Tailwind CSS의 라이브러리인 Flowbite React를 주로 사용하여 클라이언트 화면을 구성하였다.
Flowbite React는 디자인된 Component들을 import 한 번으로 쉽게 사용할 수 있어 매우 편리했다.
Mern 풀스택을 처음부터 끝까지 스스로 만들어보는 것이 주 목적이기 때문에 디자인에 크게 신경 쓰지는 않았다.
flex와 grid 다루는 것이 좀 힘들었던 것 빼면 무난하게 진행할 수 있었다.
① Layout
<Nav fluid rounded>
<NavbarBrand href="/">
<img src={mainLogo} className="mr-3 h-6 sm:h-9" alt="Logo" />
<span className="self-center whitespace-nowrap text-xl font-semibold dark:text-white">Men's Fashion</span>
</NavbarBrand>
<NavbarToggle />
<NavbarCollapse>
<NavbarLink href="/" active>
Home
</NavbarLink>
{!auth.isLoggedIn && (
<NavbarLink href="/signup">
Signup
</NavbarLink>
)}
{!auth.isLoggedIn && (
<NavbarLink href="/login">Login</NavbarLink>
)}
{auth.isLoggedIn && (
<NavbarLink href="/" onClick={auth.logout}>Logout</NavbarLink>
)}
<NavbarLink href={`/cart/${auth.userId}`}>Cart</NavbarLink>
<NavbarLink href={`/user/${auth.userId}`}>MyPage</NavbarLink>
{auth.isAdmin && (
<NavbarLink href="/product/new">NewProduct</NavbarLink>
)}
</NavbarCollapse>
</Nav>
<div className="mt-48">
<Foot className="fixed bottom-0" container>
<div className="w-full text-center">
<div className="w-full justify-between sm:flex sm:items-center sm:justify-between">
<FooterBrand
href="/"
src={mainLogo}
alt="Logo"
name="Men's Fashion"
/>
<FooterLinkGroup>
<FooterLink href="/">About</FooterLink>
<FooterLink href="/">Privacy Policy</FooterLink>
<FooterLink href="/">Licensing</FooterLink>
<FooterLink href="/">Contact</FooterLink>
</FooterLinkGroup>
</div>
<FooterDivider />
<h5 className="text-gray-500 dark:text-gray-400">쇼핑몰 구현 프로젝트</h5>
</div>
</Foot>
</div>
② Home, Category 화면
<div>
<CategoryList />
<div className="flex justify-center mt-8">
<h1 className="mb-4 text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl dark:text-white">All Products</h1>
</div>
<div className="flex justify-center flex-wrap">
{products.map((product, index) => (
<OneProduct
key={product.id}
id={product.id}
image={product.image}
name={product.name}
price={product.price}
/>
))}
</div>
</div>
③ Signup, Login, Create, Edit 화면 (Form 형식)
<div className="flex justify-center">
<Card className="w-1/2 max-w-96">
<form className="flex max-w-md flex-col gap-4" onSubmit={submitHandler}>
<div>
<div className="mb-2 block">
<Label htmlFor="name" value="Your name" />
</div>
<TextInput id="name" type="text" value={formData.name} onChange={handleChange} name="name" required shadow />
</div>
<div>
<div className="mb-2 block">
<Label htmlFor="email" value="Your email" />
</div>
<TextInput id="email" type="email" value={formData.email} onChange={handleChange} name="email" required shadow />
</div>
<div>
<div className="mb-2 block">
<Label htmlFor="password" value="Your password" />
</div>
<TextInput id="password" type="password" value={formData.password} onChange={handleChange} name="password" required shadow />
</div>
<div>
<div className="mb-2 block">
<Label htmlFor="repeatpassword" value="Repeat password" />
</div>
<TextInput id="repeatpassword" type="password" value={formData.repeatpassword} onChange={handleChange} name="repeatpassword" required shadow />
</div>
<div>
<div className="mb-2 block">
<Label htmlFor="address" value="Your address" />
</div>
<TextInput id="address" type="text" value={formData.address} onChange={handleChange} name="address" required shadow />
</div>
<div>
<div className="mb-2 block">
<Label htmlFor="phonenumber" value="Your phonenumber" />
</div>
<TextInput id="phonenumber" type="text" value={formData.phonenumber} onChange={handleChange} name="phonenumber" required shadow />
</div>
<Button type="submit">Signup new account</Button>
</form>
</Card>
</div>
④ Cart, 구매내역 화면 (Table 형식)
<div className="flex justify-center">
<Table>
<TableHead>
<TableHeadCell>Product name</TableHeadCell>
<TableHeadCell>Category</TableHeadCell>
<TableHeadCell>Price</TableHeadCell>
<TableHeadCell>Stock</TableHeadCell>
<TableHeadCell>
<span className="sr-only">Delete</span>
</TableHeadCell>
</TableHead>
<TableBody className="divide-y">
{userData?.cart.map((product) => (
<CartProduct
key={product.id}
id={product.id}
name={product.name}
category={categorySelector(Number(product.category))}
price={product.price}
stock={product.stock}
/>
))}
</TableBody>
</Table>
</div>
⑤ Product 화면
<CategoryList />
<div className="flex flex-row my-8 justify-center">
<Card className="max-w-xl basis-1/2 ml-8">
<img
alt="product"
src={product?.image}
/>
</Card>
<Card className="max-w-xl basis-1/2 mr-8">
<h2 className="text-4xl font-extrabold dark:text-white">{product?.name}</h2>
<p className="my-4 text-lg text-gray-500">
{product?.description}
</p>
<List>
<ListItem>Category: {categorySelector(Number(product?.category))}</ListItem>
<ListItem>Price: ${product?.price}</ListItem>
<ListItem>Stock: {product?.stock}</ListItem>
</List>
{auth.isLoggedIn && (
<Button onClick={putInCart}>Put in a Cart</Button>
)}
{auth.isAdmin && (
<Button onClick={goToEdit} color="success">Edit</Button>
)}
{auth.isAdmin && (
<Button onClick={deleteProduct} color="failure">Delete</Button>
)}
</Card>
</div>
<div className="flex flex-col">
{auth.isLoggedIn && (
<NewReview productId={productId} userId={auth.userId}/>
)}
{reviews.map((review, index) => (
<ReviewBlock
key={review.id}
id={review.id}
user={review.user.name}
star={review.star}
comment={review.comment}
/>
))}
</div>
⑥ My page 화면
<Card className="w-1/3">
<div className="flex flex-col items-center pb-10">
<Avatar
alt="User Image"
size="lg"
img={UserImage}
rounded
/>
<h5 className="mb-1 text-3xl font-medium text-gray-900 dark:text-white">{userData?.name}</h5>
<span className="text-sm text-gray-500 dark:text-gray-400">email: {userData?.email}</span>
<span className="text-sm text-gray-500 dark:text-gray-400">address: {userData?.address}</span>
<span className="text-sm text-gray-500 dark:text-gray-400">phone number: {userData?.phonenumber}</span>
<div className="mt-4 flex space-x-3 lg:mt-6">
<a
href={`/review/${userId}`}
className="inline-flex items-center rounded-lg bg-cyan-700 px-4 py-2 text-center text-sm font-medium text-white hover:bg-cyan-800 focus:outline-none focus:ring-4 focus:ring-cyan-300 dark:bg-cyan-600 dark:hover:bg-cyan-700 dark:focus:ring-cyan-800"
>
Show my reviews
</a>
</div>
<div className="mt-4 flex space-x-3 lg:mt-6">
<a
href={`/history/${userId}`}
className="inline-flex items-center rounded-lg bg-cyan-700 px-4 py-2 text-center text-sm font-medium text-white hover:bg-cyan-800 focus:outline-none focus:ring-4 focus:ring-cyan-300 dark:bg-cyan-600 dark:hover:bg-cyan-700 dark:focus:ring-cyan-800"
>
Show my purchase history
</a>
</div>
<div className="mt-4 flex space-x-3 lg:mt-6">
<a
href={`/edit/${userId}`}
className="inline-flex items-center rounded-lg bg-cyan-700 px-4 py-2 text-center text-sm font-medium text-white hover:bg-cyan-800 focus:outline-none focus:ring-4 focus:ring-cyan-300 dark:bg-cyan-600 dark:hover:bg-cyan-700 dark:focus:ring-cyan-800"
>
Edit my info
</a>
</div>
</div>
</Card>
⑦ send-request hook
export const useSendRequest = () => {
const activeHttpRequests = useRef([]);
const sendRequest = useCallback(
async (url, method = 'GET', body = null, headers = {}) => {
const httpAbortCtrl = new AbortController();
activeHttpRequests.current.push(httpAbortCtrl);
try {
const response = await fetch(url, {
method,
body,
headers,
signal: httpAbortCtrl.signal
});
const responseData = await response.json();
activeHttpRequests.current = activeHttpRequests.current.filter(
reqCtrl => reqCtrl !== httpAbortCtrl
);
if (!response.ok) {
throw new Error(responseData.message);
}
return responseData;
} catch (err) {
throw err;
}
},
[]
);
useEffect(() => {
return () => {
activeHttpRequests.current.forEach(abortCtrl => abortCtrl.abort());
};
}, []);
return { sendRequest };
};
'쇼핑몰 구현 프로젝트' 카테고리의 다른 글
[쇼핑몰 구현 프로젝트] 06. 상품 관련 기능 (0) | 2024.06.23 |
---|---|
[쇼핑몰 구현 프로젝트] 05. 회원가입/로그인 (0) | 2024.06.23 |
[쇼핑몰 구현 프로젝트] 04. Backend, Database 틀잡기 (0) | 2024.06.22 |
[쇼핑몰 구현 프로젝트] 02. API, DB 설계 (0) | 2024.06.07 |
[쇼핑몰 구현 프로젝트] 01. 시작 (0) | 2024.03.31 |