Strapi 및 Chakra UI가 포함된 Next.js 전자상거래 앱
이 기사에서는 이전에 만든 관리자 패널의 전자 상거래 앱을 만듭니다.
이제 정제 3 버전에서 헤드리스로 사용됩니다. 헤드리스 기능으로 원하는 모든 UI 라이브러리를 사용할 수 있습니다.
전자 상거래 클라이언트 예제 애플리케이션에서 Strapi 및 Chakra-UI을 함께 사용할 것입니다.
프로젝트 설정 수정
리파인 프로젝트를 생성하여 시작하겠습니다. superplate을 사용하여 구체화 프로젝트를 생성할 수 있습니다.
npx superplate-cli -p refine-nextjs refine-ecommerce-example
✔ What will be the name of your app · refine-ecommerce-example
✔ Package manager: · npm
✔ Do you want to using UI Framework? > No(headless)
✔ Data Provider: Strapi
✔ i18n - Internationalization: · no
superplate는 우리가 선택한 기능에 따라 신속하게 개선 프로젝트를 생성합니다. 나중에 사용할 Chakra-UI 패키지를 설치해 보겠습니다.
설치
cd refine-ecommerce-example
npm i @pankod/refine-strapi-v4
npm i @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6
우리의 정제 프로젝트와 설치가 이제 준비되었습니다! 사용을 시작해 보겠습니다.
용법
Strapi-v4에 대한 Refine 구성
pages/index.tsx:
import React from "react";
import { AppProps } from "next/app";
import Head from "next/head";
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-nextjs-router";
import { DataProvider } from "@pankod/refine-strapi-v4";
const API_URL = "https://api.strapi-multi-tenant.refine.dev/api";
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
const dataProvider = DataProvider(API_URL);
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider}
>
<Component {...pageProps} />
</Refine>
);
}
Chakra-UI 제공자 설정
pages/index.tsx:
import React from "react";
import { AppProps } from "next/app";
import Head from "next/head";
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-nextjs-router";
import { DataProvider } from "@pankod/refine-strapi-v4";
import { ChakraProvider } from "@chakra-ui/react";
const API_URL = "https://api.strapi-multi-tenant.refine.dev/api";
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
const dataProvider = DataProvider(API_URL);
return (
<Refine routerProvider={routerProvider} dataProvider={dataProvider}>
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
</Refine>
);
}
스트라피 컬렉션 만들기
우리는
store
, product
, order
로 스트래피에 3개의 컬렉션을 만들고 이들 사이의 관계를 추가했습니다. 컬렉션 생성 방법에 대한 자세한 내용은 here에서 확인할 수 있습니다.이전 Strapi Multitenancy 가이드에서 컬렉션을 만들었습니다. 이제 우리는 동일한 컬렉션을 사용할 것입니다.
Refer to the Project Collections for detailed information. →
세분화 레이아웃 만들기
리파인 헤드리스는 어떤 UI와도 관련이 없습니다. UI를 사용자 정의하는 것은 전적으로 귀하에게 달려 있습니다. 이 예제에 대한 간단한 레이아웃을 만들어 보겠습니다.
지금 만든 레이아웃에는 리파인 로고만 표시됩니다. 다음 단계에서는 레이아웃을 편집합니다.
components/Layout.tsx:
import { Box, Container, Flex, Image } from "@chakra-ui/react";
export const Layout: React.FC = ({ children }) => {
return (
<Box
display={"flex"}
flexDirection={"column"}
backgroundColor={"#eeeeee"}
minH={"100vh"}
>
<Container maxW={"container.lg"}>
<Flex justify={"space-between"} mt={4} alignSelf={"center"}>
<a href="https://refine.dev">
<Image alt="Refine Logo" src={"./refine_logo.png"} />
</a>
</Flex>
{children}
</Container>
</Box>
);
};
pages/_app.tsx:
import React from "react";
import { AppProps } from "next/app";
import Head from "next/head";
import { Refine } from "@pankod/refine-core";
import routerProvider from "@pankod/refine-nextjs-router";
import { DataProvider } from "@pankod/refine-strapi-v4";
import { ChakraProvider } from "@chakra-ui/react";
import { Layout } from "src/components";
const API_URL = "https://api.strapi-multi-tenant.refine.dev/api";
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
const dataProvider = DataProvider(API_URL);
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider}
Layout={Layout}
>
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
</Refine>
);
}
Chakra-UI를 사용한 제품 카드 디자인
Chakra-UI로 제품 카드를 디자인해 봅시다.
src/components/ProductCard.tsx
import React from "react";
import { Box, Image, Badge, Button } from "@chakra-ui/react";
export type ProductProps = {
id: string;
title: string;
description: string;
cardImage: string;
};
export const ProductCard: React.FC<ProductProps> = ({
id,
title,
description,
cardImage,
}) => {
return (
<Box maxH={"sm"} borderWidth="1px" borderRadius="lg" overflow="hidden">
<Image w={"100%"} h={200} src={cardImage} />
<Box p="6" bgColor={"gray.600"}>
<Box display="flex" alignItems="baseline" mb={2} ml={-2}>
<Badge borderRadius="full" px="2" colorScheme="teal">
New Product
</Badge>
</Box>
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
isTruncated
color={"white"}
>
{title}
</Box>
<Box color={"white"}>{}</Box>
<Box
color="white"
fontSize="sm"
display={"flex"}
mt={4}
justifyContent={"flex-end"}
></Box>
</Box>
</Box>
);
};
제품 카드 구성 요소를 만들었습니다. 이제 Strapi에서 제품을 가져와서 보여주는 프로세스로 이동해 보겠습니다.
SSR로 제품 가져오기
먼저 nextjs
getServerSideProps
함수로 제품을 가져오자.GetServerSideProps
pages/index.tsx:
import { GetServerSideProps } from "next";
import { DataProvider } from "@pankod/refine-strapi-v4";
import { IProduct } from "interfaces";
const API_URL = "https://api.strapi-multi-tenant.refine.dev/api";
export const getServerSideProps: GetServerSideProps = async (context) => {
const data = await DataProvider(API_URL).getList<IProduct>({
resource: "products",
metaData: { populate: ["image"] },
});
return {
props: { products: data },
};
};
Refine으로 제품 목록 만들기
정제
useTable
후크를 사용하여 위에서 가져온 데이터를 처리해 보겠습니다. 그런 다음 데이터를 ProductCard 구성 요소에 넣습니다.pages/index.tsx:
import { GetServerSideProps } from "next";
import { LayoutWrapper, GetListResponse, useTable } from "@pankod/refine-core";
import { DataProvider } from "@pankod/refine-strapi-v4";
import { IProduct } from "interfaces";
import { SimpleGrid } from "@chakra-ui/react";
import { ProductCard } from "src/components";
const API_URL = "https://api.strapi-multi-tenant.refine.dev/api";
type ItemProps = {
products: GetListResponse<IProduct>;
};
export const ProductList: React.FC<ItemProps> = ({ products }) => {
const { tableQueryResult } = useTable<IProduct>({
resource: "products",
queryOptions: {
initialData: products,
},
metaData: { populate: ["image"] },
});
return (
<LayoutWrapper>
<SimpleGrid columns={[1, 2, 3]} mt={6} spacing={3}>
{tableQueryResult.data?.data.map((item) => (
<ProductCard
id={item.id}
title={item.title}
description={item.description}
cardImage={
item.image
? API_URL + item.image.url
: "./error.png"
}
/>
))}
</SimpleGrid>
</LayoutWrapper>
);
};
export default ProductList;
export const getServerSideProps: GetServerSideProps = async (context) => {
const data = await DataProvider(API_URL).getList<IProduct>({
resource: "products",
metaData: { populate: ["image"] },
});
return {
props: { products: data },
};
};
스토어 기반 필터링 추가
위의 모든 제품을 가져왔습니다. 이제 매장을 가져와서 매장별 상품을 따로 나열해 보겠습니다.
먼저
useMany
함수 내에서 getServerSideProps
후크를 정제하여 매장을 가져오겠습니다. 다음으로 상점에 대한 버튼을 생성합니다. 이 버튼을 클릭하면 매장이 선택되고 useTable
setFilters
로 필터링을 수행하고 해당 매장과 관련된 제품을 나열합니다.pages/index.tsx:
export const getServerSideProps: GetServerSideProps = async (context) => {
const data = await DataProvider(API_URL).getList<IProduct>({
resource: "products",
metaData: { populate: ["image"] },
pagination: { current: 1, pageSize: 9 },
});
const { data: storesData } = await DataProvider(API_URL).getMany({
resource: "stores",
ids: ["1", "2", "3"],
});
return {
props: {
products: data,
stores: storesData,
},
};
};
pages/index.tsx:
import { GetServerSideProps } from "next";
import { LayoutWrapper, GetListResponse, useTable } from "@pankod/refine-core";
import { DataProvider } from "@pankod/refine-strapi-v4";
import { IProduct, IStore } from "interfaces";
import { Button, SimpleGrid, Flex, Text } from "@chakra-ui/react";
import { ProductCard, FilterButton } from "src/components";
const API_URL = "https://api.strapi-multi-tenant.refine.dev/api";
type ItemProps = {
products: GetListResponse<IProduct>;
stores: IStore[];
};
export const ProductList: React.FC<ItemProps> = ({ products, stores }) => {
const { tableQueryResult, setFilters } = useTable<IProduct>({
resource: "products",
queryOptions: {
initialData: products,
},
metaData: { populate: ["image"] },
});
return (
<LayoutWrapper>
<Flex mt={6} gap={2}>
<FilterButton
setFilters={() =>
setFilters([
{
field: "stores][id]",
operator: "eq",
value: undefined,
},
])
}
>
<Text fontSize={{ base: "12px", md: "14px", lg: "14px" }}>
All Products
</Text>
</FilterButton>
{stores?.map((item) => {
return (
<FilterButton
setFilters={() =>
setFilters([
{
field: "stores][id]",
operator: "eq",
value: item.id,
},
])
}
>
<Text
fontSize={{
base: "12px",
md: "14px",
lg: "14px",
}}
>
{item.title}
</Text>
</FilterButton>
);
})}
</Flex>
<SimpleGrid columns={[1, 2, 3]} mt={6} spacing={3}>
{tableQueryResult.data?.data.map((item) => (
<ProductCard
id={item.id}
title={item.title}
description={item.description}
cardImage={
item.image
? API_URL + item.image.url
: "./error.png"
}
/>
))}
</SimpleGrid>
</LayoutWrapper>
);
};
export default ProductList;
export const getServerSideProps: GetServerSideProps = async (context) => {
const data = await DataProvider(API_URL).getList<IProduct>({
resource: "products",
metaData: { populate: ["image"] },
pagination: { current: 1, pageSize: 9 },
});
const { data: storesData } = await DataProvider(API_URL).getMany({
resource: "stores",
ids: ["1", "2", "3"],
});
return {
props: {
products: data,
stores: storesData,
},
};
};
페이지 매김 추가
우리는
All Products
페이지에 모든 제품을 나열합니다. 이 페이지에 페이지 매김을 추가하고 제품을 페이지로 나눕니다. useTable 후크에서 pageSize
, current
및 setCurrent 속성을 사용하여 페이지 매김을 수행합니다.Refer to the useTable documentation for detailed information. →
pages/index.tsx:
import { GetServerSideProps } from "next";
import { LayoutWrapper, GetListResponse, useTable } from "@pankod/refine-core";
import { DataProvider } from "@pankod/refine-strapi-v4";
import { IProduct, IStore } from "interfaces";
import { Button, SimpleGrid, Flex, Text } from "@chakra-ui/react";
import { ProductCard, FilterButton } from "src/components";
const API_URL = "https://api.strapi-multi-tenant.refine.dev/api";
type ItemProps = {
products: GetListResponse<IProduct>;
stores: IStore[];
};
export const ProductList: React.FC<ItemProps> = ({ products, stores }) => {
const { tableQueryResult, setFilters, current, setCurrent, pageSize } =
useTable<IProduct>({
resource: "products",
queryOptions: {
initialData: products,
},
initialPageSize: 9,
metaData: { populate: ["image"] },
});
const totalPageCount = Math.ceil(tableQueryResult.data?.total!! / pageSize);
return (
<LayoutWrapper>
<Flex mt={6} gap={2}>
<FilterButton
setFilters={() =>
setFilters([
{
field: "stores][id]",
operator: "eq",
value: undefined,
},
])
}
>
<Text fontSize={{ base: "12px", md: "14px", lg: "14px" }}>
All Products
</Text>
</FilterButton>
{stores?.map((item) => {
return (
<FilterButton
setFilters={() =>
setFilters([
{
field: "stores][id]",
operator: "eq",
value: item.id,
},
])
}
>
<Text
fontSize={{
base: "12px",
md: "14px",
lg: "14px",
}}
>
{item.title}
</Text>
</FilterButton>
);
})}
</Flex>
<SimpleGrid columns={[1, 2, 3]} mt={6} spacing={3}>
{tableQueryResult.data?.data.map((item) => (
<ProductCard
id={item.id}
title={item.title}
description={item.description}
cardImage={
item.image
? API_URL + item.image.url
: "./error.png"
}
/>
))}
</SimpleGrid>
<Flex justify={"flex-end"} mt={4} mb={4} gap={2}>
{Array.from(Array(totalPageCount), (e, i) => {
if (current > totalPageCount) {
setCurrent(i);
}
return (
<Button
colorScheme={"teal"}
onClick={() => setCurrent(i + 1)}
>
{"Page: " + (i + 1)}
</Button>
);
})}
</Flex>
</LayoutWrapper>
);
};
export default ProductList;
export const getServerSideProps: GetServerSideProps = async (context) => {
const data = await DataProvider(API_URL).getList<IProduct>({
resource: "products",
metaData: { populate: ["image"] },
pagination: { current: 1, pageSize: 9 },
});
const { data: storesData } = await DataProvider(API_URL).getMany({
resource: "stores",
ids: ["1", "2", "3"],
});
return {
props: { products: data, stores: storesData },
};
};
Snipcart로 장바구니 및 결제 기능 추가
전자 상거래 애플리케이션에서 수행해야 하는 단계 중 하나는 장바구니 및 결제 거래입니다. 이 예에서는 이 프로세스에 Snipcart을 사용합니다.
Refer to the Snipcart documentation for detailed information. →
설치 Snipcart 위젯
pages/_app.tsx:
function MyApp({ Component, pageProps }: AppProps): JSX.Element {
const dataProvider = DataProvider(API_URL);
return (
<>
<Head>
<link rel="preconnect" href="https://app.snipcart.com" />
<link
rel="stylesheet"
href="https://cdn.snipcart.com/themes/v3.0.16/default/snipcart.css"
/>
<script
async
src="https://cdn.snipcart.com/themes/v3.0.16/default/snipcart.js"
/>
</Head>
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider}
resources={[{ name: "products" }]}
Layout={Layout}
>
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
</Refine>
<div hidden id="snipcart" data-api-key="YOUR_SNIPCART_TEST_KEY" />
</>
);
}
ProductCard 구성 요소에 "장바구니에 추가" 버튼 추가
src/components/ProductCard.tsx:
import React from "react";
import { Box, Image, Badge, Button } from "@chakra-ui/react";
export type ProductProps = {
id: string;
title: string;
description: string;
cardImage: string;
};
export const ProductCard: React.FC<ProductProps> = ({
id,
title,
description,
cardImage,
}) => {
return (
<Box
maxH={"sm"}
maxW="sm"
borderWidth="1px"
borderRadius="lg"
overflow="hidden"
>
<Image w={"100%"} h={200} src={cardImage} />
<Box p="6" bgColor={"gray.600"}>
<Box display="flex" alignItems="baseline" mb={2} ml={-2}>
<Badge borderRadius="full" px="2" colorScheme="teal">
New Product
</Badge>
</Box>
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
isTruncated
color={"white"}
>
{title}
</Box>
<Box
color="white"
fontSize="sm"
display={"flex"}
mt={4}
justifyContent={"flex-end"}
>
<Button
className="buy-button snipcart-add-item"
bgColor={"green.400"}
data-item-id={id}
data-item-price="5"
data-item-url="/"
data-item-name={title}
data-item-description={description}
data-item-image={cardImage}
>
Add to Basket
</Button>
</Box>
</Box>
</Box>
);
};
결론
Refine을 다른 프레임워크와 구별하는 가장 큰 기능 중 하나는 사용자 정의가 가능하다는 것입니다. 정제 헤드리스와 결합하여 이제 더 많은 사용자 정의 옵션을 제공합니다. 이것은 당신이 개발할 프로젝트에서 많은 편의를 제공합니다.
이 기사에서 볼 수 있듯이 우리는 이전에 수행했던 Admin Panel 의 Client 부분을 개선하여 개발했습니다. Refine은 B2B 및 B2C 애플리케이션을 제한 없이 완전히 사용자 정의할 수 있는 방식으로 개발할 수 있는 기회를 제공합니다.
Refer to the Admin side of the project →
Source Code
Live CodeSandbox Example
Check out for detailed information about refine. →
Reference
이 문제에 관하여(Strapi 및 Chakra UI가 포함된 Next.js 전자상거래 앱), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/pankod/nextjs-e-commerce-app-with-strapi-and-chakra-ui-1cf5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)