From 0e1008337217796d94197b0eeb25ac1751da6fe9 Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Tue, 5 Mar 2024 21:14:32 +0900 Subject: [PATCH] feat: implement shopping cart --- package.json | 1 + src/common/api.js | 34 +++++++++ src/common/shop/cart.js | 110 +++++++++++++++++++++++++++ src/components/views/ProductItem.jsx | 35 +++++++-- src/components/views/SignInAuth.jsx | 57 +++++++------- yarn.lock | 16 +++- 6 files changed, 221 insertions(+), 32 deletions(-) create mode 100644 src/common/api.js create mode 100644 src/common/shop/cart.js diff --git a/package.json b/package.json index 1573d17e..658351aa 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@testing-library/user-event": "14.4.3", "@types/node": "18.15.5", "@types/react": "18.0.30", + "axios": "^1.6.7", "babel-plugin-styled-components": "2.0.7", "electron-context-menu": "3.3.0", "electron-is-dev": "2.0.0", diff --git a/src/common/api.js b/src/common/api.js new file mode 100644 index 00000000..990c0399 --- /dev/null +++ b/src/common/api.js @@ -0,0 +1,34 @@ +import axios, { AxiosError } from 'axios'; + +export const API_BASE = 'https://api.chec.io/v1'; + +const api = axios.create({ + baseURL: API_BASE, +}); + +api.interceptors.request.use( + async (config) => { + return config; + }, + (error) => { + Promise.reject(error); + }, +); + +api.interceptors.response.use( + (response) => { + return response; + }, + async (error) => { + if (error.response?.status === 401) { + try { + console.log('error.response', error.response); + } catch (e) { + console.log('error', e); + } + } + return Promise.reject(error); + }, +); + +export { api }; diff --git a/src/common/shop/cart.js b/src/common/shop/cart.js new file mode 100644 index 00000000..29592d98 --- /dev/null +++ b/src/common/shop/cart.js @@ -0,0 +1,110 @@ +const token = 'sk_test_56290c1603cc68a61b59eb003647fdb91940a2cdc5b31'; + +export async function createCart() { + try { + const url = new URL(`https://api.chec.io/v1/carts`); + + const headers = { + 'X-Authorization': token, + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + + const response = await fetch(url, { + method: 'GET', + headers: headers, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('Error:', error); + throw error; + } +} + +export async function getCart(cartId) { + try { + const url = new URL(`https://api.chec.io/v1/carts/${cartId}`); + + const headers = { + 'X-Authorization': token, + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + + const response = await fetch(url, { + method: 'GET', + headers: headers, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('Error:', error); + throw error; + } +} + +export async function addItemToCart(cartId, body) { + try { + const url = new URL(`https://api.chec.io/v1/carts/${cartId}`); + + const headers = { + 'X-Authorization': token, + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + + const response = await fetch(url, { + method: 'POST', + headers: headers, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('Error:', error); + throw error; + } +} + +export async function removeItemFromCart(cartId, line_item_id) { + try { + const url = new URL(`https://api.chec.io/v1/carts/${cartId}/items/${line_item_id}`); + + const headers = { + 'X-Authorization': token, + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + + const response = await fetch(url, { + method: 'DELETE', + headers: headers, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('Error:', error); + throw error; + } +} diff --git a/src/components/views/ProductItem.jsx b/src/components/views/ProductItem.jsx index 9c8bb96f..b8e2dcdd 100644 --- a/src/components/views/ProductItem.jsx +++ b/src/components/views/ProductItem.jsx @@ -6,6 +6,8 @@ import { TEMP_PRODUCTS_DATA } from './Shop'; import Categories from '../Shop/Categories'; import FooterSection from '../FooterSection'; import SidebarMenu from '../SidebarMenu'; +import { useEffect, useState } from 'react'; +import { createCart, addItemToCart } from '../../common/shop/cart'; const Image = styled.img` max-width: unset !important; @@ -14,8 +16,31 @@ const Image = styled.img` `; const ProductItem = () => { - const { id } = useParams(); - const { name, price, src, description } = TEMP_PRODUCTS_DATA.find((product) => String(product.id) === String(id)); + const { id: productId } = useParams(); + const { name, price, src, description } = TEMP_PRODUCTS_DATA.find((product) => String(product.id) === String(productId)); + const [cartId, setCartId] = useState(''); + const [quantity, setQuantity] = useState(1); + + useEffect(() => { + const getCartId = async () => { + const cart = await createCart(); + setCartId(cart?.id); + }; + getCartId(); + }, []); + + const handlePurchase = async () => { + await addItemToCart(cartId, { + id: 'prod_8XO3wp77QNlYAz', + quantity: quantity, + }); + + alert('Item added to cart'); + }; + + const handleQuantityChange = (e) => { + setQuantity(e.target.value); + }; return ( <> @@ -37,7 +62,7 @@ const ProductItem = () => {
-
+

{name}

@@ -49,12 +74,12 @@ const ProductItem = () => {

Quantity:

- +


- +


diff --git a/src/components/views/SignInAuth.jsx b/src/components/views/SignInAuth.jsx index f72a301b..cd169f82 100644 --- a/src/components/views/SignInAuth.jsx +++ b/src/components/views/SignInAuth.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import { Header, Logo, AboutContent } from '../styled/views/Home.styled'; import { Link } from 'react-router-dom'; import { useParams } from 'react-router-dom'; @@ -7,39 +7,44 @@ import { useNavigate } from 'react-router-dom'; const SignInAuth = () => { const { id } = useParams(); const navigate = useNavigate(); + const loaded = useRef(false); useEffect(() => { if (!id) return; - const getUser = async () => { - try { - const url = new URL('https://api.chec.io/v1/customers/exchange-token'); - const headers = { - 'X-Authorization': 'sk_test_56290c1603cc68a61b59eb003647fdb91940a2cdc5b31', - 'Content-Type': 'application/json', - Accept: 'application/json', - }; + if (!loaded.current) { + loaded.current = true; - const response = await fetch(url, { - method: 'POST', - headers: headers, - body: JSON.stringify({ token: id }), - }).then((response) => response.json()); + const getUser = async () => { + try { + const url = new URL('https://api.chec.io/v1/customers/exchange-token'); - const { customer_id, jwt } = response; + const headers = { + 'X-Authorization': 'sk_test_56290c1603cc68a61b59eb003647fdb91940a2cdc5b31', + 'Content-Type': 'application/json', + Accept: 'application/json', + }; - console.log('customer_id', customer_id); - console.log('jwt', jwt); - localStorage.setItem('customer_id', customer_id); - localStorage.setItem('jwt', jwt); + const response = await fetch(url, { + method: 'POST', + headers: headers, + body: JSON.stringify({ token: id }), + }).then((response) => response.json()); - navigate('/shop'); - } catch (error) { - console.error('Failed', error); - } - }; - getUser(); - }, [id]); + const { customer_id, jwt } = response; + + localStorage.setItem('login_token', id); + localStorage.setItem('customer_id', customer_id); + localStorage.setItem('jwt', jwt); + + navigate('/shop'); + } catch (error) { + console.error('Failed', error); + } + }; + getUser(); + } + }, [id, loaded]); return ( <> diff --git a/yarn.lock b/yarn.lock index 1fc23a43..b6f17f47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4616,6 +4616,15 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" +axios@^1.6.7: + version "1.6.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" + integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + dependencies: + follow-redirects "^1.15.4" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.1.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -8053,7 +8062,7 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.9: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -follow-redirects@^1.14.0: +follow-redirects@^1.14.0, follow-redirects@^1.15.4: version "1.15.5" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== @@ -13213,6 +13222,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + psl@^1.1.33: version "1.9.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"