updateCartViewDisplay()}
11 | onKeyDown={() => updateCartViewDisplay()}
12 | role="button"
13 | tabIndex="-1"
14 | aria-label="Close cart view"
15 | />
16 | );
17 | };
18 |
19 | export default Blur;
20 |
--------------------------------------------------------------------------------
/src/components/layout/layout.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from "react";
2 | import NavBar from "./nav-bar";
3 | import Blur from "./blur";
4 | import CartView from "../cart-view/cart-view";
5 | import DisplayContext from "../../context/display-context";
6 | import * as styles from "../../styles/layout.module.css";
7 | import "../../styles/globals.css";
8 |
9 | const Layout = ({ location, children }) => {
10 | const { cartView } = useContext(DisplayContext);
11 | const [isCheckout, setIsCheckout] = useState(false);
12 |
13 | useEffect(() => {
14 | if (
15 | location.location.pathname === "/checkout" ||
16 | location.location.pathname === "/payment"
17 | ) {
18 | setIsCheckout(true);
19 | } else {
20 | setIsCheckout(false);
21 | }
22 | }, [location]);
23 |
24 | return (
25 |
26 |
27 |
28 |
29 |
30 | {children}
31 |
32 |
33 | );
34 | };
35 |
36 | export default Layout;
37 |
--------------------------------------------------------------------------------
/src/components/layout/nav-bar.jsx:
--------------------------------------------------------------------------------
1 | import { Link } from "gatsby";
2 | import React, { useContext } from "react";
3 | import DisplayContext from "../../context/display-context";
4 | import StoreContext from "../../context/store-context";
5 | import { quantity, sum } from "../../utils/helper-functions";
6 | import { BiShoppingBag } from "react-icons/bi";
7 | import * as styles from "../../styles/nav-bar.module.css";
8 | import MedusaLogo from "../../images/medusa-logo.svg";
9 |
10 | const NavBar = ({ isCheckout }) => {
11 | const { updateCartViewDisplay } = useContext(DisplayContext);
12 | const { cart } = useContext(StoreContext);
13 |
14 | return (
15 |
16 |
17 |
18 |
19 | {!isCheckout ? (
20 |
updateCartViewDisplay()}>
21 | Cart
22 | {" "}
23 |
24 | {cart.items.length > 0 ? cart.items.map(quantity).reduce(sum) : 0}
25 |
26 |
27 | ) : null}
28 |
29 | );
30 | };
31 |
32 | export default NavBar;
33 |
--------------------------------------------------------------------------------
/src/context/display-context.js:
--------------------------------------------------------------------------------
1 | import React, { useReducer } from "react";
2 |
3 | export const defaultDisplayContext = {
4 | cartView: false,
5 | orderSummary: false,
6 | checkoutStep: 1,
7 | updateCartViewDisplay: () => {},
8 | updateOrderSummaryDisplay: () => {},
9 | updateCheckoutStep: () => {},
10 | dispatch: () => {},
11 | };
12 |
13 | const DisplayContext = React.createContext(defaultDisplayContext);
14 | export default DisplayContext;
15 |
16 | const reducer = (state, action) => {
17 | switch (action.type) {
18 | case "updateCartViewDisplay":
19 | return { ...state, cartView: !state.cartView };
20 | case "updateOrderSummaryDisplay":
21 | return { ...state, orderSummary: !state.orderSummary };
22 | case "updateCheckoutStep":
23 | return { ...state, checkoutStep: action.payload };
24 | default:
25 | return state;
26 | }
27 | };
28 |
29 | export const DisplayProvider = ({ children }) => {
30 | const [state, dispatch] = useReducer(reducer, defaultDisplayContext);
31 |
32 | const updateCartViewDisplay = () => {
33 | dispatch({ type: "updateCartViewDisplay" });
34 | };
35 |
36 | const updateOrderSummaryDisplay = () => {
37 | dispatch({ type: "updateOrderSummaryDisplay" });
38 | };
39 |
40 | const updateCheckoutStep = (step) => {
41 | dispatch({ type: "updateCheckoutStep", payload: step });
42 | };
43 |
44 | return (
45 |
54 | {children}
55 |
56 | );
57 | };
58 |
--------------------------------------------------------------------------------
/src/context/store-context.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useReducer, useRef } from "react";
2 | import { createClient } from "../utils/client";
3 |
4 | export const defaultStoreContext = {
5 | adding: false,
6 | cart: {
7 | items: [],
8 | },
9 | order: {},
10 | products: [],
11 | currencyCode: "eur",
12 | /**
13 | *
14 | * @param {*} variantId
15 | * @param {*} quantity
16 | * @returns
17 | */
18 | addVariantToCart: async () => {},
19 | createCart: async () => {},
20 | removeLineItem: async () => {},
21 | updateLineItem: async () => {},
22 | setShippingMethod: async () => {},
23 | updateAddress: async () => {},
24 | createPaymentSession: async () => {},
25 | completeCart: async () => {},
26 | retrieveOrder: async () => {},
27 | dispatch: () => {},
28 | };
29 |
30 | const StoreContext = React.createContext(defaultStoreContext);
31 | export default StoreContext;
32 |
33 | const reducer = (state, action) => {
34 | switch (action.type) {
35 | case "setCart":
36 | return {
37 | ...state,
38 | cart: action.payload,
39 | currencyCode: action.payload.region.currency_code,
40 | };
41 | case "setOrder":
42 | return {
43 | ...state,
44 | order: action.payload,
45 | };
46 | case "setProducts":
47 | return {
48 | ...state,
49 | products: action.payload,
50 | };
51 | default:
52 | return state;
53 | }
54 | };
55 |
56 | const client = createClient();
57 |
58 | export const StoreProvider = ({ children }) => {
59 | const [state, dispatch] = useReducer(reducer, defaultStoreContext);
60 | const stateCartId = useRef();
61 |
62 | useEffect(() => {
63 | stateCartId.current = state.cart.id;
64 | }, [state.cart]);
65 |
66 | useEffect(() => {
67 | let cartId;
68 | if (localStorage) {
69 | cartId = localStorage.getItem("cart_id");
70 | }
71 |
72 | if (cartId) {
73 | client.carts.retrieve(cartId).then((data) => {
74 | dispatch({ type: "setCart", payload: data.cart });
75 | });
76 | } else {
77 | client.carts.create(cartId).then((data) => {
78 | dispatch({ type: "setCart", payload: data.cart });
79 | if (localStorage) {
80 | localStorage.setItem("cart_id", data.cart.id);
81 | }
82 | });
83 | }
84 |
85 | client.products.list().then((data) => {
86 | dispatch({ type: "setProducts", payload: data.products });
87 | });
88 | }, []);
89 |
90 | const createCart = () => {
91 | if (localStorage) {
92 | localStorage.removeItem("cart_id");
93 | }
94 | client.carts.create().then((data) => {
95 | dispatch({ type: "setCart", payload: data.cart });
96 | });
97 | };
98 |
99 | const setPaymentSession = async (provider) => {
100 | client.carts
101 | .setPaymentSession(state.cart.id, {
102 | provider_id: provider,
103 | })
104 | .then((data) => {
105 | dispatch({ type: "setCart", payload: data.cart });
106 | return data;
107 | });
108 | };
109 |
110 | const addVariantToCart = async ({ variantId, quantity }) => {
111 | client.carts.lineItems
112 | .create(state.cart.id, {
113 | variant_id: variantId,
114 | quantity: quantity,
115 | })
116 | .then((data) => {
117 | dispatch({ type: "setCart", payload: data.cart });
118 | });
119 | };
120 |
121 | const removeLineItem = async (lineId) => {
122 | client.carts.lineItems.delete(state.cart.id, lineId).then((data) => {
123 | dispatch({ type: "setCart", payload: data.cart });
124 | });
125 | };
126 |
127 | const updateLineItem = async ({ lineId, quantity }) => {
128 | client.carts.lineItems
129 | .update(state.cart.id, lineId, { quantity: quantity })
130 | .then((data) => {
131 | dispatch({ type: "setCart", payload: data.cart });
132 | });
133 | };
134 |
135 | const getShippingOptions = async () => {
136 | const data = await client.shippingOptions
137 | .listCartOptions(state.cart.id)
138 | .then((data) => data);
139 |
140 | if (data) {
141 | return data.shipping_options;
142 | } else {
143 | return undefined;
144 | }
145 | };
146 |
147 | const setShippingMethod = async (id) => {
148 | return await client.carts
149 | .addShippingMethod(state.cart.id, {
150 | option_id: id,
151 | })
152 | .then((data) => {
153 | dispatch({ type: "setCart", payload: data.cart });
154 | return data;
155 | });
156 | };
157 |
158 | const createPaymentSession = async () => {
159 | return await client.carts
160 | .createPaymentSessions(state.cart.id)
161 | .then((data) => {
162 | dispatch({ type: "setCart", payload: data.cart });
163 | return data;
164 | });
165 | };
166 |
167 | const completeCart = async () => {
168 | const data = await client.carts
169 | .complete(state.cart.id)
170 | .then((data) => data);
171 |
172 | if (data) {
173 | return data.data;
174 | } else {
175 | return undefined;
176 | }
177 | };
178 |
179 | const retrieveOrder = async (orderId) => {
180 | const data = await client.orders.retrieve(orderId).then((data) => data);
181 |
182 | if (data) {
183 | return data.order;
184 | } else {
185 | return undefined;
186 | }
187 | };
188 |
189 | const updateAddress = (address, email) => {
190 | client.carts
191 | .update(state.cart.id, {
192 | shipping_address: address,
193 | billing_address: address,
194 | email: email,
195 | })
196 | .then((data) => {
197 | dispatch({ type: "setCart", payload: data.cart });
198 | });
199 | };
200 |
201 | return (
202 |
219 | {children}
220 |
221 | );
222 | };
223 |
--------------------------------------------------------------------------------
/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medusajs/gatsby-starter-medusa-simple/796275c0612a63e2e136ae1a29ede7522ffbb2d0/src/images/icon.png
--------------------------------------------------------------------------------
/src/images/medusa-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/medusajs/gatsby-starter-medusa-simple/796275c0612a63e2e136ae1a29ede7522ffbb2d0/src/images/medusa-logo.jpg
--------------------------------------------------------------------------------
/src/images/medusa-logo.svg:
--------------------------------------------------------------------------------
1 |
2 | medusa-logo-full-colour-rgb
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Link } from "gatsby"
3 |
4 | // styles
5 | const pageStyles = {
6 | color: "#232129",
7 | padding: "96px",
8 | fontFamily: "-apple-system, Roboto, sans-serif, serif",
9 | }
10 | const headingStyles = {
11 | marginTop: 0,
12 | marginBottom: 64,
13 | maxWidth: 320,
14 | }
15 |
16 | const paragraphStyles = {
17 | marginBottom: 48,
18 | }
19 | const codeStyles = {
20 | color: "#8A6534",
21 | padding: 4,
22 | backgroundColor: "#FFF4DB",
23 | fontSize: "1.25rem",
24 | borderRadius: 4,
25 | }
26 |
27 | // markup
28 | const NotFoundPage = () => {
29 | return (
30 |
31 | Not found
32 | Page not found
33 |
34 | Sorry{" "}
35 |
36 | 😔
37 | {" "}
38 | we couldn’t find what you were looking for.
39 |
40 | {process.env.NODE_ENV === "development" ? (
41 | <>
42 |
43 | Try creating a page in src/pages/
.
44 |
45 | >
46 | ) : null}
47 |
48 | Go home.
49 |
50 |
51 | )
52 | }
53 |
54 | export default NotFoundPage
55 |
--------------------------------------------------------------------------------
/src/pages/checkout.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CheckoutStep from "../components/checkout/checkout-step";
3 |
4 | const Checkout = () => {
5 | return
;
6 | };
7 |
8 | export default Checkout;
9 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { FaGithub } from "react-icons/fa";
3 | import StoreContext from "../context/store-context";
4 | import { graphql } from "gatsby";
5 | import * as styles from "../styles/home.module.css";
6 | import { Link } from "gatsby";
7 | import { formatPrices } from "../utils/format-price";
8 |
9 | // markup
10 | const IndexPage = ({ data }) => {
11 | const { cart, products } = useContext(StoreContext);
12 |
13 | return (
14 |
15 |
16 |
17 |
18 | Medusa + Gatsby Starter{" "}
19 |
20 | 🚀
21 |
22 |
23 |
24 | Build blazing-fast client applications on top of a modular headless
25 | commerce engine. Integrate seamlessly with any 3rd party tools for a
26 | best-in-breed commerce stack.
27 |
28 |
72 |
104 |
105 |
106 |
Demo Products
107 |
108 | {products &&
109 | products.map((p) => {
110 | return (
111 |
112 |
113 |
114 |
{p.title}
115 |
{formatPrices(cart, p.variants[0])}
116 |
117 |
118 |
119 | );
120 | })}
121 |
122 |
123 |
124 |
125 | );
126 | };
127 |
128 | export const query = graphql`
129 | query VersionQuery {
130 | site {
131 | siteMetadata {
132 | version
133 | }
134 | }
135 | }
136 | `;
137 |
138 | export default IndexPage;
139 |
--------------------------------------------------------------------------------
/src/pages/payment.js:
--------------------------------------------------------------------------------
1 | import { Link } from "gatsby";
2 | import React, { useContext, useEffect, useState } from "react";
3 | import StoreContext from "../context/store-context";
4 | import * as itemStyles from "../styles/cart-view.module.css";
5 | import * as styles from "../styles/payment.module.css";
6 | import { formatPrice } from "../utils/helper-functions";
7 |
8 | const style = {
9 | height: "100vh",
10 | width: "100%",
11 | display: "flex",
12 | flexDirection: "column",
13 | justifyContent: "center",
14 | alignItems: "center",
15 | textAlign: "center",
16 | };
17 |
18 | const Payment = () => {
19 | const [order, setOrder] = useState();
20 | const { cart, completeCart, createCart } = useContext(StoreContext);
21 |
22 | useEffect(() => {
23 | if (cart.items.length > 0) {
24 | completeCart().then((order) => {
25 | setOrder(order);
26 | createCart();
27 | });
28 | }
29 | }, []);
30 |
31 | return !order ? (
32 |
33 |
Hang on while we validate your payment...
34 |
35 | ) : (
36 |
37 |
38 |
Order Summary
39 |
Thank you for your order!
40 |
41 |
42 | {order.items
43 | .sort((a, b) => {
44 | const createdAtA = new Date(a.created_at),
45 | createdAtB = new Date(b.created_at);
46 |
47 | if (createdAtA < createdAtB) return -1;
48 | if (createdAtA > createdAtB) return 1;
49 | return 0;
50 | })
51 | .map((i) => {
52 | return (
53 |
54 |
55 |
56 |
57 | {/* Replace with a product thumbnail/image */}
58 |
59 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | {i.title}
76 |
77 |
78 | Size: {i.variant.title}
79 |
80 |
81 | Price:{" "}
82 | {formatPrice(i.unit_price, order.currency_code)}
83 |
84 |
85 | Quantity: {i.quantity}
86 |
87 |
88 |
89 |
90 |
91 |
92 | );
93 | })}
94 |
95 |
96 |
97 |
Subtotal
98 |
{formatPrice(order.subtotal, order.region.currency_code)}
99 |
100 |
101 |
Shipping
102 |
103 | {formatPrice(order.shipping_total, order.region.currency_code)}
104 |
105 |
106 |
107 |
Total
108 |
{formatPrice(order.total, order.region.currency_code)}
109 |
110 |
111 |
112 |
An order comfirmation will be sent to you at {order.email}
113 |
114 |
115 | );
116 | };
117 |
118 | export default Payment;
119 |
--------------------------------------------------------------------------------
/src/pages/product/[id].js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect, useState } from "react";
2 | import { BiShoppingBag } from "react-icons/bi";
3 | import StoreContext from "../../context/store-context";
4 | import * as styles from "../../styles/product.module.css";
5 | import { createClient } from "../../utils/client";
6 | import { formatPrices } from "../../utils/format-price";
7 | import { getSlug, resetOptions } from "../../utils/helper-functions";
8 |
9 | const Product = ({ location }) => {
10 | const { cart, addVariantToCart } = useContext(StoreContext);
11 | const [options, setOptions] = useState({
12 | variantId: "",
13 | quantity: 0,
14 | size: "",
15 | });
16 |
17 | const [product, setProduct] = useState(undefined);
18 | const client = createClient();
19 |
20 | useEffect(() => {
21 | const getProduct = async () => {
22 | const slug = getSlug(location.pathname);
23 | const response = await client.products.retrieve(slug);
24 | setProduct(response.product);
25 | };
26 |
27 | getProduct();
28 | }, [location.pathname]);
29 |
30 | useEffect(() => {
31 | if (product) {
32 | setOptions(resetOptions(product));
33 | }
34 | }, [product]);
35 |
36 | const handleQtyChange = (action) => {
37 | if (action === "inc") {
38 | if (
39 | options.quantity <
40 | product.variants.find(({ id }) => id === options.variantId)
41 | .inventory_quantity
42 | )
43 | setOptions({
44 | variantId: options.variantId,
45 | quantity: options.quantity + 1,
46 | size: options.size,
47 | });
48 | }
49 | if (action === "dec") {
50 | if (options.quantity > 1)
51 | setOptions({
52 | variantId: options.variantId,
53 | quantity: options.quantity - 1,
54 | size: options.size,
55 | });
56 | }
57 | };
58 |
59 | const handleAddToBag = () => {
60 | addVariantToCart({
61 | variantId: options.variantId,
62 | quantity: options.quantity,
63 | });
64 | if (product) setOptions(resetOptions(product));
65 | };
66 |
67 | return product && cart.id ? (
68 |
69 |
70 |
71 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
{product.title}
83 |
84 |
{formatPrices(cart, product.variants[0])}
85 |
86 |
Select Size
87 |
88 | {product.variants
89 | .slice(0)
90 | .reverse()
91 | .map((v) => {
92 | return (
93 |
99 | setOptions({
100 | variantId: v.id,
101 | quantity: options.quantity,
102 | size: v.title,
103 | })
104 | }
105 | >
106 | {v.title}
107 |
108 | );
109 | })}
110 |
111 |
112 |
113 |
Select Quantity
114 |
115 | handleQtyChange("dec")}
118 | >
119 | -
120 |
121 | {options.quantity}
122 | handleQtyChange("inc")}
125 | >
126 | +
127 |
128 |
129 |
130 |
handleAddToBag()}>
131 | Add to bag
132 |
133 |
134 |
135 |
136 | Product Description
137 |
138 |
139 |
{product.description}
140 |
141 |
142 |
143 |
144 |
145 | ) : null;
146 | };
147 |
148 | export default Product;
149 |
--------------------------------------------------------------------------------
/src/styles/blur.module.css:
--------------------------------------------------------------------------------
1 | .blur {
2 | position: absolute;
3 | top: 0;
4 | bottom: 0;
5 | right: 0;
6 | width: 100vw;
7 | height: 100vh;
8 | background: rgba(0, 0, 0, 0.5);
9 | opacity: 0;
10 | visibility: hidden;
11 | cursor: pointer;
12 | transition: all 0.2s ease-in-out;
13 | z-index: 10;
14 | }
15 |
16 | .active {
17 | opacity: 1;
18 | visibility: visible;
19 | }
--------------------------------------------------------------------------------
/src/styles/cart-view.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | --py: 35px;
3 |
4 | position: fixed;
5 | min-width: 460px;
6 | height: 100vh;
7 | max-height: 100vh;
8 | -webkit-box-shadow: var(--shade);
9 | box-shadow: var(--shade);
10 | background: white;
11 | z-index: 11;
12 | display: flex;
13 | flex-direction: column;
14 | overflow: hidden;
15 | justify-content: space-between;
16 | right: -460px;
17 | top: 0;
18 | transition: -webkit-transform 0.5s ease;
19 | transition: transform 0.5s ease;
20 | -webkit-transform: translateX(110%);
21 | -ms-transform: translateX(110%);
22 | transform: translateX(110%);
23 | }
24 |
25 | .active {
26 | -webkit-transform: translateX(-460px);
27 | -ms-transform: translateX(-460px);
28 | transform: translateX(-460px);
29 | }
30 |
31 | .top {
32 | display: flex;
33 | align-items: center;
34 | justify-content: space-between;
35 | padding: 15px var(--py);
36 | }
37 |
38 | .closebtn {
39 | background: transparent;
40 | border: none;
41 | cursor: pointer;
42 | }
43 |
44 | .subtotal {
45 | display: flex;
46 | align-items: center;
47 | justify-content: space-between;
48 | padding: 15px var(--py);
49 | }
50 |
51 | .bottom {
52 | padding: 15px var(--py);
53 | }
54 |
55 | .overview {
56 | flex-grow: 1;
57 | overflow-y: scroll;
58 | scrollbar-width: thin;
59 | scrollbar-color: var(--logo-color-400) transparent;
60 | }
61 |
62 | .overview::-webkit-scrollbar {
63 | width: 12px;
64 | border-radius: 12px;
65 | }
66 |
67 | .overview::-webkit-scrollbar-track {
68 | background: transparent;
69 | border-radius: 12px;
70 | }
71 |
72 | .overview::-webkit-scrollbar-thumb {
73 | background-color: var(--logo-color-400);
74 | border-radius: 20px;
75 | border: 1px solid var(--bg);
76 | }
77 |
78 | .product {
79 | padding: 24px var(--py) 0;
80 | margin-top: 0;
81 | position: relative;
82 | min-height: 120px;
83 | display: flex;
84 | }
85 |
86 | .mid {
87 | display: flex;
88 | flex-direction: column;
89 | }
90 |
91 | .price {
92 | margin: 0;
93 | }
94 |
95 | .selector {
96 | display: flex;
97 | align-items: center;
98 | }
99 |
100 | .product figure {
101 | background: var(--bg);
102 | width: 126px;
103 | height: 189px;
104 | margin: 0;
105 | margin-right: 1rem;
106 | }
107 |
108 | .placeholder {
109 | width: 100%;
110 | height: 100%;
111 | cursor: pointer;
112 | }
113 |
114 | .controls {
115 | display: flex;
116 | flex-direction: column;
117 | justify-content: space-around;
118 | }
119 |
120 | .remove {
121 | background: transparent;
122 | border: none;
123 | cursor: pointer;
124 | padding: 0;
125 | text-align: left;
126 | text-decoration: underline;
127 | color: lightgrey;
128 | transition: color 0.1s ease-in;
129 | }
130 |
131 | .remove:hover {
132 | color: var(--logo-color-900);
133 | }
134 |
135 | .size {
136 | font-size: var(--fz-sm);
137 | color: grey;
138 | }
139 |
140 | .ticker {
141 | width: 25px;
142 | text-align: center;
143 | user-select: none;
144 | }
145 |
146 | .qtybtn {
147 | background: transparent;
148 | border: transparent;
149 | color: grey;
150 | transition: color 0.1s ease-in;
151 | cursor: pointer;
152 | }
153 |
154 | .qtybtn:hover {
155 | color: var(--logo-color-900);
156 | }
157 |
158 | .checkoutbtn {
159 | width: 100%;
160 | font-size: 1.125rem;
161 | min-height: 3rem;
162 | padding: 0.5rem 0;
163 | align-self: center;
164 | display: inline-flex;
165 | align-items: center;
166 | justify-content: center;
167 | background: var(--logo-color-900);
168 | color: white;
169 | border-radius: 8px;
170 | transition: background 0.2s ease-in;
171 | font-weight: 500;
172 | cursor: pointer;
173 | border: none;
174 | }
175 |
176 | .checkoutbtn:hover {
177 | background: var(--logo-color-1000);
178 | }
179 |
180 | @media (max-width: 876px) {
181 |
182 | .container {
183 | width: 100%;
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/src/styles/checkout-step.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | height: 100vh;
4 | }
5 |
6 | .steps {
7 | width: 60%;
8 | height: 100vh;
9 | padding: 110px 88px 28px;
10 | display: flex;
11 | flex-direction: column;
12 | overflow-y: scroll;
13 | scrollbar-width: thin;
14 | scrollbar-color: var(--logo-color-400) transparent;
15 | }
16 |
17 | .summary {
18 | width: 40%;
19 | display: flex;
20 | flex-direction: column;
21 | }
22 |
23 | .orderBtn {
24 | width: 100%;
25 | font-size: 1rem;
26 | min-height: 3rem;
27 | padding: 0.5rem 0;
28 | align-self: center;
29 | display: inline-flex;
30 | align-items: center;
31 | justify-content: center;
32 | border-radius: 8px;
33 | transition: background 0.2s ease-in;
34 | font-weight: 500;
35 | cursor: pointer;
36 | border: none;
37 | display: none;
38 | justify-self: flex-end;
39 | background: transparent;
40 | }
41 |
42 | .orderBtn:hover {
43 | background: var(--logo-color-100);
44 | }
45 |
46 | .breadcrumbs {
47 | display: flex;
48 | }
49 |
50 | .breadcrumbs p {
51 | margin-right: 0.5rem;
52 | color: grey;
53 | transition: color 0.2s ease-in;
54 | }
55 |
56 | .breadcrumbs p:last-child {
57 | margin-right: 0;
58 | }
59 |
60 | .breadcrumbs p.activeStep {
61 | color: black;
62 | }
63 |
64 | @media (max-width: 876px) {
65 | .container {
66 | flex-direction: column;
67 | }
68 | .steps {
69 | padding: 0px 22px;
70 | width: 100%;
71 | height: 100%;
72 | }
73 | .breadcrumbs {
74 | margin-top: 6rem;
75 | }
76 | .orderBtn {
77 | margin-bottom: 2rem;
78 | display: block;
79 | }
80 | }
--------------------------------------------------------------------------------
/src/styles/checkout-summary.module.css:
--------------------------------------------------------------------------------
1 | .spinnerContainer {
2 | width: 100%;
3 | height: 100vh;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | }
8 |
9 | .container {
10 | --py: 35px;
11 |
12 | position: fixed;
13 | width: 40%;
14 | height: 100vh;
15 | max-height: 100vh;
16 | -webkit-box-shadow: var(--shade);
17 | box-shadow: var(--shade);
18 | background: white;
19 | z-index: 11;
20 | display: flex;
21 | flex-direction: column;
22 | overflow: hidden;
23 | justify-content: space-between;
24 | right: 0;
25 | position: fixed;
26 | top: 0;
27 | }
28 |
29 | .closeBtn {
30 | background: transparent;
31 | border: none;
32 | cursor: pointer;
33 | display: none;
34 | }
35 |
36 | .breakdown {
37 | display: flex;
38 | align-items: center;
39 | justify-content: space-between;
40 | padding: 10px var(--py);
41 | }
42 |
43 | .total {
44 | display: flex;
45 | align-items: center;
46 | justify-content: space-between;
47 | padding: 10px var(--py) 20px;
48 | font-weight: 700;
49 | }
50 |
51 | .total p {
52 | margin: 0;
53 | }
54 |
55 | .breakdown p {
56 | margin: 0;
57 | }
58 |
59 | @media (max-width: 876px) {
60 | .container {
61 | --py: 35px;
62 |
63 | position: fixed;
64 | height: calc(100vh - 20px);
65 | max-height: calc(100vh - 20px);
66 | -webkit-box-shadow: var(--shade);
67 | box-shadow: var(--shade);
68 | background: white;
69 | z-index: 11;
70 | display: flex;
71 | flex-direction: column;
72 | overflow: hidden;
73 | justify-content: space-between;
74 | top: 20px;
75 | bottom: 0;
76 | border-radius: 8px;
77 | transition: -webkit-transform 0.5s ease;
78 | transition: transform 0.5s ease;
79 | -webkit-transform: translateY(110%);
80 | -ms-transform: translateY(110%);
81 | transform: translateY(110%);
82 | width: 100%;
83 | transition: -webkit-transform 0.5s ease;
84 | transition: transform 0.5s ease;
85 | }
86 |
87 | .active {
88 | -webkit-transform: translateY(0px);
89 | -ms-transform: translateY(0px);
90 | transform: translateY(0px);
91 | }
92 |
93 | .closeBtn {
94 | display: block;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --logo-color-1000: #30363d;
3 | --logo-color-900: #454b54;
4 | --logo-color-400: #a3b1c7;
5 | --logo-color-100: #454b5411;
6 | --bg: #f7f9f9;
7 |
8 | /* Font sizes */
9 | --fz-s: 12px;
10 | --fz-sm: 14px;
11 | --fz-m: 16px;
12 | --fz-ml: 18px;
13 | --fz-l: 22px;
14 | --fz-xl: 24px;
15 |
16 | /* box-shadow */
17 | --shade: 0 1px 5px rgba(0, 0, 0, 0.2);
18 |
19 | /* Nav */
20 | --nav-height: 68px;
21 | }
22 |
23 | * {
24 | box-sizing: border-box;
25 | }
26 |
27 | html,
28 | body {
29 | padding: 0;
30 | margin: 0;
31 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
32 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
33 | }
34 |
35 | a {
36 | color: inherit;
37 | text-decoration: none;
38 | }
39 |
40 | button {
41 | cursor: pointer;
42 | }
43 |
44 | button:disabled,
45 | button:disabled:hover {
46 | cursor: not-allowed;
47 | background: lightgrey;
48 | color: black;
49 | }
50 |
51 | * {
52 | box-sizing: border-box;
53 | }
54 |
--------------------------------------------------------------------------------
/src/styles/home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 88px;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .main {
11 | padding: 5rem 0;
12 | flex: 1;
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | align-items: center;
17 | }
18 |
19 | .title {
20 | margin: 0;
21 | line-height: 1.15;
22 | font-size: clamp(2rem, 8vw, 4rem);
23 | }
24 |
25 | .description {
26 | line-height: 1.5;
27 | font-size: clamp(1rem, 2vw, 1.5rem);
28 | }
29 |
30 | .tag {
31 | border-radius: 5px;
32 | padding: 0.75rem;
33 | font-size: var(--fz-s);
34 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
35 | Bitstream Vera Sans Mono, Courier New, monospace;
36 | margin-right: 1rem;
37 | }
38 |
39 | .grid {
40 | display: flex;
41 | align-items: center;
42 | justify-content: center;
43 | flex-wrap: wrap;
44 | }
45 |
46 | .products {
47 | display: flex;
48 | flex-direction: column;
49 | align-items: flex-start;
50 | width: 100%;
51 | margin-top: 3rem;
52 | }
53 |
54 | .card {
55 | margin-right: 1rem;
56 | padding: 1.5rem;
57 | text-align: left;
58 | color: inherit;
59 | text-decoration: none;
60 | border: 1px solid #eaeaea;
61 | border-radius: 10px;
62 | transition: color 0.15s ease, border-color 0.15s ease;
63 | }
64 |
65 | .card:hover,
66 | .card:focus,
67 | .card:active {
68 | color: var(--logo-color-900);
69 | border-color: var(--logo-color-900);
70 | }
71 |
72 | .card h2 {
73 | margin: 0 0 .5rem 0;
74 | font-size: 1.25rem;
75 | }
76 |
77 | .card p {
78 | margin: 0;
79 | font-size: 1rem;
80 | line-height: 1.5;
81 | }
82 |
83 | .logo {
84 | height: 1em;
85 | margin-left: 0.5rem;
86 | }
87 |
88 | .hero {
89 | text-align: left;
90 | max-width: 800px;
91 | }
92 |
93 | .links {
94 | display: flex;
95 | align-items: center;
96 | }
97 |
98 | .btn {
99 | font-size: 1.125rem;
100 | min-height: 3rem;
101 | min-width: 3rem;
102 | padding: 0.5rem 1.25rem;
103 | align-self: center;
104 | display: inline-flex;
105 | align-items: center;
106 | background: var(--logo-color-900);
107 | color: white;
108 | border-radius: 8px;
109 | transition: background .2s ease-in;
110 | font-weight: 500;
111 | border: none;
112 | }
113 |
114 | .btn:hover {
115 | background: var(--logo-color-1000);
116 | }
117 |
118 | .btn svg {
119 | margin-left: 0.5rem;
120 | font-size: var(--fz-l);
121 | }
122 |
123 | .btn:hover svg {
124 | transform: scale(1.1);
125 | transform-origin: center;
126 | -webkit-animation: heartbeat 1.5s infinite both;
127 | animation: heartbeat 1.5s infinite both;
128 | }
129 |
130 | .links .btn:first-child {
131 | margin-right: 1rem;
132 | }
133 |
134 | .links .btn:last-child {
135 | background: transparent;
136 | color: black;
137 | font-weight: 400;
138 | }
139 |
140 | .links .btn:last-child:hover {
141 | background: var(--logo-color-100);
142 | }
143 |
144 | .tags {
145 | display: flex;
146 | align-items: center;
147 | flex-wrap: nowrap;
148 | margin-bottom: 3rem;
149 | }
150 |
151 | @media (max-width: 876px) {
152 | .grid {
153 | width: 100%;
154 | flex-direction: column;
155 | }
156 |
157 | .container {
158 | padding: 0 22px;
159 | }
160 |
161 | .card {
162 | width: 100%;
163 | margin-right: 0;
164 | margin-bottom: 1rem;
165 | }
166 | }
167 |
168 | @-webkit-keyframes heartbeat {
169 | from {
170 | -webkit-transform: scale(1.0);
171 | }
172 | 20% {
173 | -webkit-transform: scale(1.0);
174 | }
175 | 50% {
176 | -webkit-transform: scale(.90);
177 | }
178 | 100% {
179 | -webkit-transform: scale(1.0);
180 | }
181 | }
182 | @keyframes heartbeat {
183 | from {
184 | -webkit-transform: scale(1.0);
185 | transform: scale(1.0);
186 | }
187 | 20% {
188 | -webkit-transform: scale(1.0);
189 | transform: scale(1.0);
190 | }
191 | 50% {
192 | -webkit-transform: scale(.90);
193 | transform: scale(.90);
194 | }
195 | 100% {
196 | -webkit-transform: scale(1.0);
197 | transform: scale(1.0);
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/styles/information-step.module.css:
--------------------------------------------------------------------------------
1 | .styledform {
2 | width: 100%;
3 | }
4 |
5 | .sharedrow {
6 | display: flex;
7 | }
8 |
9 | .sharedrow div:first-of-type {
10 | margin-left: 0;
11 | margin-right: 10px;
12 | }
13 |
14 | .sharedrow div {
15 | margin-left: 10px;
16 | margin-right: 10px;
17 | }
18 |
19 | .sharedrow div:last-of-type {
20 | margin-right: 0;
21 | }
22 |
23 | .fieldcontainer {
24 | width: 100%;
25 | background: white;
26 | border-radius: 8px;
27 | border: 1px solid lightgrey;
28 | margin: 5px 0;
29 | display: flex;
30 | align-items: center;
31 | }
32 |
33 | .styledfield {
34 | height: 40px;
35 | width: 100%;
36 | padding: 10px;
37 | border-radius: 8px;
38 | background: transparent;
39 | border: none;
40 | }
41 |
42 | .formbtn {
43 | font-size: 1.125rem;
44 | min-height: 3rem;
45 | min-width: 3rem;
46 | padding: 0.5rem 1.25rem;
47 | align-self: center;
48 | display: inline-flex;
49 | align-items: center;
50 | background: var(--logo-color-900);
51 | color: white;
52 | border-radius: 8px;
53 | transition: background .2s ease-in;
54 | font-weight: 500;
55 | border: none;
56 | }
57 |
58 | .formbtn:hover {
59 | background: var(--logo-color-1000);
60 | }
61 |
62 | .spinner {
63 | display: flex;
64 | align-items: center;
65 | justify-content: center;
66 | width: 100%;
67 | padding: 4rem;
68 | }
69 |
70 | .btncontainer {
71 | margin-top: 1.5rem;
72 | display: flex;
73 | justify-content: flex-end;
74 | align-items: center;
75 | }
--------------------------------------------------------------------------------
/src/styles/injectable-payment-card.module.css:
--------------------------------------------------------------------------------
1 | .cardForm {
2 | background: white;
3 | box-shadow: var(--shade);
4 | padding: 2rem 2rem;
5 | border-radius: 8px;
6 | }
7 |
8 | .stepBack {
9 | background: transparent;
10 | border: none;
11 | display: flex;
12 | align-items: center;
13 | padding: 0;
14 | font-size: var(--fz-m);
15 | }
16 |
17 | .stepBack svg {
18 | margin-right: .5rem;
19 | }
20 |
21 | .controls {
22 | display: flex;
23 | align-items: center;
24 | justify-content: space-between;
25 | margin-top: 2rem;
26 | }
27 |
28 | .payBtn {
29 | font-size: 1.125rem;
30 | min-height: 3rem;
31 | min-width: 3rem;
32 | padding: 0.5rem 1.25rem;
33 | align-self: center;
34 | display: inline-flex;
35 | align-items: center;
36 | background: var(--logo-color-900);
37 | color: white;
38 | border-radius: 8px;
39 | transition: background .2s ease-in;
40 | font-weight: 500;
41 | border: none;
42 | }
43 |
44 | .payBtn:hover {
45 | background: var(--logo-color-1000);
46 | }
--------------------------------------------------------------------------------
/src/styles/input-field.module.css:
--------------------------------------------------------------------------------
1 | .colors {
2 | --error-900: #DB5461;
3 | --error-400: #d67b84;
4 | }
5 |
6 | .container {
7 | width: 100%;
8 | display: flex;
9 | align-items: baseline;
10 | flex-direction: column;
11 | margin: 5px 0;
12 | }
13 |
14 | .fieldcontainer {
15 | width: 100%;
16 | background: white;
17 | border-radius: 8px;
18 | border: 1px solid lightgrey;
19 | display: flex;
20 | align-items: center;
21 | margin-top: .1rem;
22 | position: relative;
23 | transition: all .1s ease-in;
24 | }
25 |
26 | .errorfield {
27 | --error-900: #f0ada6;
28 | --error-400: #fef1f2;
29 | border-color: var(--error-900);
30 | background: var(--error-400);
31 | }
32 |
33 | .errortext {
34 | margin: 0;
35 | align-self: flex-end;
36 | font-size: var(--fz-s);
37 | color: #e07367;
38 | }
39 |
40 | .erroricon {
41 | color: #eb948b;
42 | font-size: 18px;
43 | margin-right: 10px;
44 | position: absolute;
45 | right: 0;
46 | }
47 |
48 | .fill {
49 | color: transparent;
50 | margin: 0;
51 | font-size: var(--fz-s);
52 | }
53 |
54 | .styledfield {
55 | height: 40px;
56 | width: 100%;
57 | border-radius: 8px;
58 | background: transparent;
59 | border: none;
60 | padding: 10px;
61 | outline: none;
62 | }
63 |
64 | .styledselect {
65 | height: 40px;
66 | width: 100%;
67 | border-radius: 8px;
68 | background: transparent;
69 | border: none;
70 | padding: 10px;
71 | outline: none;
72 | -webkit-appearance: none;
73 | }
74 |
75 | .fetching {
76 | height: 40px;
77 | width: 100%;
78 | border-radius: 8px;
79 | background: transparent;
80 | border: none;
81 | padding: 10px;
82 | outline: none;
83 | background: var(--logo-color-100);
84 | }
--------------------------------------------------------------------------------
/src/styles/layout.module.css:
--------------------------------------------------------------------------------
1 | .noscroll {
2 | overflow: hidden;
3 | height: 100vh;
4 | }
5 |
--------------------------------------------------------------------------------
/src/styles/nav-bar.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 | padding: 20px 88px;
6 | position: absolute;
7 | width: 100%;
8 | }
9 |
10 | .container h1 {
11 | margin: 0;
12 | }
13 |
14 | .logo {
15 | font-size: var(--fz-xl);
16 | color: var(--logo-color-900);
17 | }
18 |
19 | .btn {
20 | border: none;
21 | background: transparent;
22 | display: flex;
23 | align-items: center;
24 | font-size: var(--fz-sm);
25 | cursor: pointer;
26 | }
27 |
28 | .btn span {
29 | margin-left: 0.75rem;
30 | margin-right: 0.75rem;
31 | }
32 |
33 | .btn svg {
34 | font-size: var(--fz-ml);
35 | }
36 |
37 | @media (max-width: 876px) {
38 | .container {
39 | padding: 20px 22px;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/styles/payment.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | margin: 0 auto;
6 | padding: 0 88px;
7 | max-width: 560px;
8 | min-height: 100vh;
9 | margin-bottom: 2rem;
10 | }
11 |
12 | .header {
13 | border-bottom: 1px solid var(--logo-color-100);
14 | margin-top: 4rem;
15 | }
16 |
17 | .items {
18 | padding: 22px 0;
19 | border-bottom: 1px solid var(--logo-color-100);
20 | margin-bottom: .5rem;
21 | }
22 |
23 | .item {
24 | margin-bottom: .5rem;
25 | }
26 |
27 | .price {
28 | display: flex;
29 | justify-content: space-between;
30 | padding: .5rem 0;
31 | }
32 |
33 | .total {
34 | font-weight: 700;
35 | border-bottom: 1px solid var(--logo-color-100);
36 | }
37 |
38 | @media (max-width: 876px) {
39 | .container {
40 | padding: 0 22px;
41 | width: 100%;
42 | max-width: 100%;
43 | }
44 | }
--------------------------------------------------------------------------------
/src/styles/product.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | min-height: 100vh;
4 | display: flex;
5 | }
6 |
7 | .image {
8 | width: 60%;
9 | height: 100vh;
10 | margin: 0;
11 | }
12 |
13 | .placeholder {
14 | height: 100%;
15 | width: 100%;
16 | background: var(--bg);
17 | }
18 |
19 | .info {
20 | display: flex;
21 | flex-direction: column;
22 | justify-content: center;
23 | padding: 1rem 3rem;
24 | }
25 |
26 | .sizebtn {
27 | padding: 0.7rem;
28 | border: none;
29 | background: lightgrey;
30 | border-radius: 2px;
31 | height: 38px;
32 | width: 38px;
33 | margin-right: 0.5rem;
34 | transition: all 0.2s ease-in;
35 | }
36 |
37 | .sizebtn:hover {
38 | filter: brightness(0.9);
39 | }
40 |
41 | .selected {
42 | background: var(--logo-color-900);
43 | color: white;
44 | }
45 |
46 | .ticker {
47 | width: 35px;
48 | height: 35px;
49 | text-align: center;
50 | display: flex;
51 | align-items: center;
52 | justify-content: center;
53 | }
54 |
55 | .qty {
56 | display: flex;
57 | align-items: center;
58 | }
59 |
60 | .selection {
61 | margin: 2rem 0;
62 | }
63 |
64 | .selection p {
65 | margin: 0;
66 | margin-bottom: 0.7rem;
67 | }
68 |
69 | .qtybtn {
70 | height: 38px;
71 | width: 38px;
72 | border: none;
73 | background: none;
74 | transition: all 0.2s ease-in;
75 | border-radius: 2px;
76 | }
77 |
78 | .qtybtn:hover,
79 | .qtybtn:focus {
80 | background: var(--logo-color-100);
81 | }
82 |
83 | .addbtn {
84 | font-size: 1.125rem;
85 | min-height: 3rem;
86 | min-width: 3rem;
87 | padding: 0.5rem 1.25rem;
88 | align-self: center;
89 | display: inline-flex;
90 | align-items: center;
91 | background: var(--logo-color-900);
92 | color: white;
93 | border-radius: 4px;
94 | transition: background 0.2s ease-in;
95 | font-weight: 500;
96 | border: none;
97 | }
98 |
99 | .addbtn svg {
100 | margin-left: 0.7rem;
101 | }
102 |
103 | .addbtn:hover {
104 | background: var(--logo-color-1000);
105 | }
106 |
107 | .tabs {
108 | margin-top: 2rem;
109 | max-width: 500px;
110 | }
111 |
112 | .tabtitle {
113 | background: transparent;
114 | border: none;
115 | padding: .5rem 0;
116 | font-size: var(--fz-m);
117 | border-bottom: 1px solid var(--logo-color-400);
118 | }
119 |
120 | @media (max-width: 876px) {
121 | .container {
122 | flex-direction: column;
123 | }
124 |
125 | .image,
126 | .info {
127 | width: 100%
128 | }
129 |
130 | .image {
131 | height: 50vh;
132 | }
133 |
134 | .info {
135 | margin-top: 1rem;
136 | padding: 0 22px;
137 | }
138 | }
--------------------------------------------------------------------------------
/src/styles/shipping-method.module.css:
--------------------------------------------------------------------------------
1 | .shippingOption {
2 | display: flex;
3 | align-items: center;
4 | box-shadow: var(--shade);
5 | justify-content: space-between;
6 | background: white;
7 | border-radius: 8px;
8 | padding: 1rem 2rem;
9 | cursor: pointer;
10 | }
11 |
12 | .shippingOption div {
13 | display: flex;
14 | align-items: center;
15 | }
16 |
17 | .chosen {
18 | border: 1px solid var(--logo-color-400)
19 | }
20 |
--------------------------------------------------------------------------------
/src/styles/shipping-step.module.css:
--------------------------------------------------------------------------------
1 | .spinner {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | width: 100%;
6 | padding: 4rem;
7 | height: 530px;
8 | }
9 |
10 | .container {
11 | margin-top: 20px;
12 | flex-grow: 1;
13 | }
14 |
15 | .shippingOption {
16 | display: flex;
17 | align-items: center;
18 | box-shadow: var(--shade);
19 | justify-content: space-between;
20 | background: white;
21 | border-radius: 8px;
22 | padding: 1rem;
23 | }
24 |
25 | .shippingOption div {
26 | display: flex;
27 | align-items: center;
28 | }
29 |
30 | .stepBack {
31 | background: transparent;
32 | border: none;
33 | display: flex;
34 | align-items: center;
35 | padding: 0;
36 | font-size: var(--fz-m);
37 | }
38 |
39 | .stepBack svg {
40 | margin-right: 0.5rem;
41 | }
42 |
43 | .controls {
44 | display: flex;
45 | align-items: center;
46 | justify-content: space-between;
47 |
48 | }
49 |
50 | .nextBtn {
51 | font-size: 1.125rem;
52 | min-height: 3rem;
53 | min-width: 3rem;
54 | padding: 0.5rem 1.25rem;
55 | align-self: center;
56 | display: inline-flex;
57 | align-items: center;
58 | background: var(--logo-color-900);
59 | color: white;
60 | border-radius: 8px;
61 | transition: background 0.2s ease-in;
62 | font-weight: 500;
63 | border: none;
64 | }
65 |
66 | .nextBtn:hover {
67 | background: var(--logo-color-1000);
68 | }
69 |
70 | .error {
71 | display: flex;
72 | opacity: 0;
73 | visibility: hidden;
74 | align-items: center;
75 | transition: all 0.1s ease-in;
76 | color: #db5461;
77 | }
78 |
79 | .error p {
80 | margin-left: 0.5rem;
81 | }
82 |
83 | .error.active {
84 | opacity: 1;
85 | visibility: visible;
86 | }
87 |
--------------------------------------------------------------------------------
/src/styles/step-overview.module.css:
--------------------------------------------------------------------------------
1 | .step {
2 | display: flex;
3 | background: white;
4 | border-radius: 8px;
5 | box-shadow: var(--shade);
6 | padding: 2rem 2rem;
7 | align-items: center;
8 | font-size: var(--fz-s);
9 | margin-bottom: 0.5em;
10 | color: grey;
11 | }
12 |
13 | .stepInfo {
14 | margin-right: 0.75em;
15 | min-width: 0px;
16 | flex: 1 1 0%;
17 | white-space: nowrap;
18 | overflow: hidden;
19 | text-overflow: ellipsis;
20 | }
21 |
22 | .detail {
23 | width: 80px;
24 | font-weight: 600;
25 | }
26 |
27 | .edit {
28 | background: transparent;
29 | border: none;
30 | transition: background .2s ease-in;
31 | padding: .5rem 1rem;
32 | border-radius: 4px;
33 | }
34 |
35 | .edit:hover {
36 | background: var(--logo-color-100);
37 | }
38 |
--------------------------------------------------------------------------------
/src/utils/client.js:
--------------------------------------------------------------------------------
1 | import Medusa from "@medusajs/medusa-js";
2 |
3 | const BACKEND_URL = process.env.GATSBY_STORE_URL || "http://localhost:9000";
4 |
5 | export const createClient = () => new Medusa({ baseUrl: BACKEND_URL });
6 |
--------------------------------------------------------------------------------
/src/utils/format-price.js:
--------------------------------------------------------------------------------
1 | function getTaxRate(cart) {
2 | if ("tax_rate" in cart) {
3 | return cart.tax_rate / 100;
4 | } else if (cart.region) {
5 | return cart.region && cart.region.tax_rate / 100;
6 | }
7 | return 0;
8 | }
9 |
10 | export function formatMoneyAmount(moneyAmount, digits, taxRate = 0) {
11 | let locale = "en-US";
12 |
13 | return new Intl.NumberFormat(locale, {
14 | style: "currency",
15 | currency: moneyAmount.currencyCode,
16 | minimumFractionDigits: digits,
17 | }).format(moneyAmount.amount * (1 + taxRate / 100));
18 | }
19 |
20 | export function getVariantPrice(cart, variant) {
21 | let taxRate = getTaxRate(cart);
22 |
23 | let moneyAmount = variant.prices.find(
24 | (p) =>
25 | p.currency_code.toLowerCase() === cart.region.currency_code.toLowerCase()
26 | );
27 |
28 | if (moneyAmount && moneyAmount.amount) {
29 | return (moneyAmount.amount * (1 + taxRate)) / 100;
30 | }
31 |
32 | return undefined;
33 | }
34 |
35 | export function formatPrices(cart, variant, digits = 2) {
36 | if (!cart || !cart.region || !variant) return;
37 | if (!variant.prices) return `15.00 EUR`;
38 | return formatMoneyAmount(
39 | {
40 | currencyCode: cart.region.currency_code,
41 | amount: getVariantPrice(cart, variant),
42 | },
43 | digits
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/src/utils/helper-functions.js:
--------------------------------------------------------------------------------
1 | export const quantity = (item) => {
2 | return item.quantity;
3 | };
4 |
5 | export const sum = (prev, next) => {
6 | return prev + next;
7 | };
8 |
9 | export const formatPrice = (price, currency) => {
10 | return `${(price / 100).toFixed(2)} ${currency.toUpperCase()}`;
11 | };
12 |
13 | export const getSlug = (path) => {
14 | const tmp = path.split("/");
15 | return tmp[tmp.length - 1];
16 | };
17 |
18 | export const resetOptions = (product) => {
19 | const variantId = product.variants.slice(0).reverse()[0].id;
20 | const size = product.variants.slice(0).reverse()[0].title;
21 | return {
22 | variantId: variantId,
23 | quantity: 1,
24 | size: size,
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/src/utils/stripe.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is a singleton to ensure we only instantiate Stripe once.
3 | */
4 | import { loadStripe } from "@stripe/stripe-js";
5 | const STRIPE_API_KEY = process.env.GATSBY_STRIPE_KEY || null;
6 | let stripePromise;
7 | const getStripe = () => {
8 | if (!stripePromise) {
9 | stripePromise = loadStripe(STRIPE_API_KEY);
10 | }
11 | return stripePromise;
12 | };
13 | export default getStripe;
14 |
--------------------------------------------------------------------------------