├── .env.local
├── .eslintrc.json
├── .gitignore
├── README.md
├── components
├── Cart.js
├── Nav.js
├── Product.js
└── User.js
├── lib
├── context.js
├── formatMoney.js
├── getStripe.js
└── query.js
├── next.config.js
├── package-lock.json
├── package.json
├── pages
├── _app.js
├── api
│ ├── auth
│ │ └── [...auth0].js
│ └── stripe.js
├── index.js
├── product
│ └── [slug].js
├── profile.js
└── success.js
├── public
├── favicon.ico
├── shiba-success.png
└── vercel.svg
└── styles
├── CartStyles.js
├── Gallery.js
├── NavStyles.js
├── ProductDetails.js
├── ProductStyle.js
└── globals.css
/.env.local:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_BACKEND_API="http://localhost:1337/graphql"
2 | BASE_URL="http://localhost:3000"
3 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_e3nGTeSuODBpgGnaKncucoi300fI60ZQna"
4 | NEXT_PUBLIC_STRIPE_SECRET_KEY="sk_test_51ErX3zJvB7fsxaM1HrkMSpk2gLRtczuCgNBCN0TucFgX78i9am4a6WKVKbGJqmtXO4dMODHorg2TAO8ChOLer9Sj00RF9GA3yj"
5 | AUTH0_SECRET='f2c4c4d5f29dc65c15ca6fa4535cbc4a34f497def9ea70c6f38b17556a3496de'
6 | AUTH0_BASE_URL="http://localhost:3000"
7 | AUTH0_ISSUER_BASE_URL="https://dev-kthrisfg.us.auth0.com/"
8 | AUTH0_CLIENT_ID="4Ye8LRQxPeV58drmikNMG8xBI1divs1n"
9 | AUTH0_CLIENT_SECRET="6R4z5Pj0hiBhu3bvWYxQWpwVFJ4BNShhG1BTitKEDkTGZ1LyTB0bdiACRUDvwQuR"
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | # .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/components/Cart.js:
--------------------------------------------------------------------------------
1 | import {
2 | CartStyle,
3 | Card,
4 | EmptyStyle,
5 | CartWrapper,
6 | CardInfo,
7 | Checkout,
8 | } from "../styles/CartStyles";
9 | import { Quantity } from "../styles/ProductDetails";
10 | import { FaShoppingCart } from "react-icons/fa";
11 | import { AiFillPlusCircle, AiFillMinusCircle } from "react-icons/ai";
12 | //Import State
13 | import { useStateContext } from "../lib/context";
14 | import getStripe from "../lib/getStripe";
15 |
16 | export default function Cart() {
17 | const { cartItems, setShowCart, onAdd, onRemove, totalPrice } =
18 | useStateContext();
19 |
20 | //Payment
21 | const handleCheckout = async () => {
22 | const stripePromise = await getStripe();
23 | const response = await fetch("/api/stripe", {
24 | method: "POST",
25 | headers: {
26 | "Content-Type": "application/json",
27 | },
28 | body: JSON.stringify(cartItems),
29 | });
30 | const data = await response.json();
31 | await stripePromise.redirectToCheckout({ sessionId: data.id });
32 | };
33 |
34 | return (
35 | setShowCart(false)}
40 | >
41 | e.stopPropagation()}
48 | >
49 | {cartItems.length < 1 && (
50 |
55 | You have more shopping to do 😉
56 |
57 |
58 | )}
59 | {cartItems.length >= 1 &&
60 | cartItems.map((item) => {
61 | return (
62 |
68 |
69 |
70 | {item.title}
71 | {item.price}$
72 |
73 | Quantity
74 |
77 | {item.quantity}
78 |
81 |
82 |
83 |
84 | );
85 | })}
86 |
87 | {cartItems.length >= 1 && (
88 |
89 |
Subtotal ${totalPrice}
90 |
91 |
92 | )}
93 |
94 |
95 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/components/Nav.js:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import { FiShoppingBag } from "react-icons/fi";
3 | import { NavStyles, NavItems } from "../styles/NavStyles";
4 | import Cart from "./Cart";
5 | import { useStateContext } from "../lib/context";
6 | const { AnimatePresence, motion } = require("framer-motion");
7 | import User from "./User";
8 |
9 | export default function Nav() {
10 | const { showCart, setShowCart, totalQuantities } = useStateContext();
11 |
12 | return (
13 |
14 | Styled.
15 |
16 |
17 | setShowCart(true)}>
18 | {totalQuantities > 0 && (
19 |
20 | {totalQuantities}
21 |
22 | )}
23 |
24 |
Cart
25 |
26 |
27 | {showCart && }
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/components/Product.js:
--------------------------------------------------------------------------------
1 | import { ProductStyles } from "../styles/ProductStyle";
2 | import Link from "next/link";
3 |
4 | export default function Product({ product }) {
5 | //Extract from props
6 | const { title, price, image, slug } = product.attributes;
7 | return (
8 |
9 |
10 |
11 |

12 |
13 |
14 | {title}
15 | ${price}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/components/User.js:
--------------------------------------------------------------------------------
1 | import { FaUserCircle } from "react-icons/fa";
2 | import { useRouter } from "next/router";
3 | import { useUser } from "@auth0/nextjs-auth0";
4 | import styled from "styled-components";
5 |
6 | export default function User() {
7 | const route = useRouter();
8 | const { user, error, isLoading } = useUser();
9 | if (!user)
10 | return (
11 | route.push(`/api/auth/login`)}>
12 |
13 |
Login
14 |
15 | );
16 | return (
17 | route.push(`/profile`)}>
18 |
19 | {user.name}
20 |
21 | );
22 | }
23 |
24 | const Profile = styled.div`
25 | img {
26 | border-radius: 50%;
27 | width: 1.5rem;
28 | height: 1.5rem;
29 | }
30 | `;
31 |
--------------------------------------------------------------------------------
/lib/context.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useState } from "react";
2 |
3 | const Context = createContext();
4 |
5 | export const StateContext = ({ children }) => {
6 | //Ourt application state
7 | const [showCart, setShowCart] = useState(false);
8 | const [cartItems, setCartItems] = useState([]);
9 | const [qty, setQty] = useState(1);
10 | const [totalPrice, setTotalPrice] = useState(0);
11 | const [totalQuantities, setTotalQuantitites] = useState(0);
12 |
13 | //Increase product countity
14 | const increaseQty = () => {
15 | setQty((prevQty) => prevQty + 1);
16 | };
17 | //Decrease product quantity
18 | const decreaseQty = () => {
19 | setQty((prevQty) => {
20 | if (prevQty - 1 < 1) return 1;
21 | return prevQty - 1;
22 | });
23 | };
24 | //Add Product To Cart
25 | const onAdd = (product, quantity) => {
26 | //Total Price
27 | setTotalPrice(
28 | (prevTotalPrice) => prevTotalPrice + product.price * quantity
29 | );
30 | //Increase total quantity
31 | setTotalQuantitites(
32 | (prevTotalQuantities) => prevTotalQuantities + quantity
33 | );
34 | //Check if product is in the cart
35 | const exist = cartItems.find((item) => item.slug === product.slug);
36 | if (exist) {
37 | setCartItems(
38 | cartItems.map((item) =>
39 | item.slug === product.slug
40 | ? { ...exist, quantity: exist.quantity + quantity }
41 | : item
42 | )
43 | );
44 | } else {
45 | setCartItems([...cartItems, { ...product, quantity: quantity }]);
46 | }
47 | };
48 | //Remove product
49 | const onRemove = (product) => {
50 | //Set Total Price
51 | setTotalPrice((prevTotalPrice) => prevTotalPrice - product.price);
52 |
53 | //Remove from total quantities
54 | setTotalQuantitites((prevTotalQuantities) => prevTotalQuantities - 1);
55 |
56 | //Check if product exists
57 | const exist = cartItems.find((item) => item.slug === product.slug);
58 | if (exist.quantity === 1) {
59 | setCartItems(cartItems.filter((item) => item.slug !== product.slug));
60 | } else {
61 | setCartItems(
62 | cartItems.map((item) =>
63 | item.slug === product.slug
64 | ? { ...exist, quantity: exist.quantity - 1 }
65 | : item
66 | )
67 | );
68 | }
69 | };
70 |
71 | return (
72 |
87 | {children}
88 |
89 | );
90 | };
91 |
92 | export const useStateContext = () => useContext(Context);
93 |
--------------------------------------------------------------------------------
/lib/formatMoney.js:
--------------------------------------------------------------------------------
1 | export default function formatMoney(amount = 0) {
2 | const options = {
3 | style: "currency",
4 | currency: "USD",
5 | minimumFractionDigits: 2,
6 | };
7 | // check if its a clean dollar amount
8 | if (amount % 100 === 0) {
9 | options.minimumFractionDigits = 0;
10 | }
11 |
12 | const formatter = Intl.NumberFormat("en-US", options);
13 | return formatter.format(amount / 100);
14 | }
15 |
--------------------------------------------------------------------------------
/lib/getStripe.js:
--------------------------------------------------------------------------------
1 | import { loadStripe } from "@stripe/stripe-js";
2 |
3 | let stripePromise;
4 |
5 | const getStripe = async () => {
6 | if (!stripePromise) {
7 | stripePromise = await loadStripe(
8 | `${process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}`
9 | );
10 | }
11 |
12 | return stripePromise;
13 | };
14 |
15 | export default getStripe;
16 |
--------------------------------------------------------------------------------
/lib/query.js:
--------------------------------------------------------------------------------
1 | export const PRODUCT_QUERY = `query{
2 | products{
3 | data{
4 | attributes{
5 | description
6 | title
7 | slug
8 | price
9 | image{
10 | data{
11 | attributes{
12 | formats
13 | }
14 | }
15 | }
16 | }
17 | }
18 | }
19 | }
20 | `;
21 |
22 | export const GET_PRODUCT_QUERY = `
23 | query getProducts($slug:String!){
24 | products(filters: {slug :{eq: $slug}}){
25 | data{
26 | attributes{
27 | title
28 | slug
29 | description
30 | price
31 | image{
32 | data{
33 | attributes{
34 | formats
35 | }
36 | }
37 | }
38 | }
39 | }
40 | }
41 | }`;
42 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | compiler: {
5 | // ssr and displayName are configured by default
6 | styledComponents: true,
7 | },
8 | async redirects() {
9 | return [
10 | {
11 | source: "/canceled",
12 | destination: "/",
13 | permanent: true,
14 | },
15 | ];
16 | },
17 | };
18 |
19 | module.exports = nextConfig;
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@auth0/nextjs-auth0": "^1.9.0",
13 | "@stripe/react-stripe-js": "^1.8.1",
14 | "@stripe/stripe-js": "^1.31.0",
15 | "framer-motion": "^6.3.11",
16 | "graphql": "^16.5.0",
17 | "graphql-request": "^4.3.0",
18 | "next": "12.1.6",
19 | "react": "18.1.0",
20 | "react-dom": "18.1.0",
21 | "react-hot-toast": "^2.2.0",
22 | "react-icons": "^4.4.0",
23 | "react-loading-skeleton": "^3.1.0",
24 | "stripe": "^9.8.0",
25 | "styled-components": "^5.3.5",
26 | "urql": "^2.2.2"
27 | },
28 | "devDependencies": {
29 | "eslint": "8.17.0",
30 | "eslint-config-next": "12.1.6"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "../styles/globals.css";
2 | import { createClient, Provider } from "urql";
3 | import Nav from "../components/Nav";
4 | import { StateContext } from "../lib/context";
5 | import { UserProvider } from "@auth0/nextjs-auth0";
6 | import { Toaster } from "react-hot-toast";
7 |
8 | const client = createClient({ url: process.env.NEXT_PUBLIC_BACKEND_API });
9 |
10 | function MyApp({ Component, pageProps }) {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | export default MyApp;
25 |
--------------------------------------------------------------------------------
/pages/api/auth/[...auth0].js:
--------------------------------------------------------------------------------
1 | import { handleAuth } from "@auth0/nextjs-auth0";
2 |
3 | export default handleAuth();
4 |
--------------------------------------------------------------------------------
/pages/api/stripe.js:
--------------------------------------------------------------------------------
1 | import Stripe from "stripe";
2 | const stripe = new Stripe(`${process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY}`);
3 | import { getSession } from "@auth0/nextjs-auth0";
4 |
5 | export default async function handler(req, res) {
6 | const session = getSession(req, res);
7 | const user = session?.user;
8 | console.log(user);
9 | if (user) {
10 | const stripeId = user["http://localhost:3000/stripe_customer_id"];
11 | if (req.method === "POST") {
12 | try {
13 | // Create Checkout Sessions from body params.
14 | const session = await stripe.checkout.sessions.create({
15 | submit_type: "pay",
16 | mode: "payment",
17 | payment_method_types: ["card"],
18 | customer: stripeId,
19 | shipping_address_collection: {
20 | allowed_countries: ["US", "CA"],
21 | },
22 |
23 | allow_promotion_codes: true,
24 | shipping_options: [
25 | { shipping_rate: "shr_1L7HGSJvB7fsxaM1DbSs7DeV" },
26 | { shipping_rate: "shr_1L7HGyJvB7fsxaM1OpMXx2Fn" },
27 | ],
28 | line_items: req.body.map((item) => {
29 | return {
30 | price_data: {
31 | currency: "usd",
32 | product_data: {
33 | name: item.title,
34 | images: [item.image.data.attributes.formats.thumbnail.url],
35 | },
36 | unit_amount: item.price * 100,
37 | },
38 | adjustable_quantity: {
39 | enabled: true,
40 | minimum: 1,
41 | },
42 | quantity: item.quantity,
43 | };
44 | }),
45 | success_url: `${req.headers.origin}/success?&session_id={CHECKOUT_SESSION_ID}`,
46 | cancel_url: `${req.headers.origin}/canceled`,
47 | });
48 | res.status(200).json(session);
49 | } catch (err) {
50 | res.status(err.statusCode || 500).json(err.message);
51 | }
52 | } else {
53 | res.setHeader("Allow", "POST");
54 | res.status(405).end("Method Not Allowed");
55 | }
56 | } else {
57 | console.log("nope");
58 | if (req.method === "POST") {
59 | try {
60 | // Create Checkout Sessions from body params.
61 | const session = await stripe.checkout.sessions.create({
62 | submit_type: "pay",
63 | mode: "payment",
64 | payment_method_types: ["card"],
65 | shipping_address_collection: {
66 | allowed_countries: ["US", "CA"],
67 | },
68 |
69 | allow_promotion_codes: true,
70 | shipping_options: [
71 | { shipping_rate: "shr_1L7HGSJvB7fsxaM1DbSs7DeV" },
72 | { shipping_rate: "shr_1L7HGyJvB7fsxaM1OpMXx2Fn" },
73 | ],
74 | line_items: req.body.map((item) => {
75 | return {
76 | price_data: {
77 | currency: "usd",
78 | product_data: {
79 | name: item.title,
80 | images: [item.image.data.attributes.formats.thumbnail.url],
81 | },
82 | unit_amount: item.price * 100,
83 | },
84 | adjustable_quantity: {
85 | enabled: true,
86 | minimum: 1,
87 | },
88 | quantity: item.quantity,
89 | };
90 | }),
91 | success_url: `${req.headers.origin}/success?&session_id={CHECKOUT_SESSION_ID}`,
92 | cancel_url: `${req.headers.origin}/canceled`,
93 | });
94 | res.status(200).json(session);
95 | } catch (err) {
96 | res.status(err.statusCode || 500).json(err.message);
97 | }
98 | } else {
99 | res.setHeader("Allow", "POST");
100 | res.status(405).end("Method Not Allowed");
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 | import { PRODUCT_QUERY } from "../lib/query";
3 | import { useQuery } from "urql";
4 | import Product from "../components/Product";
5 | import { Gallery } from "../styles/Gallery";
6 | import Skeleton from "react-loading-skeleton";
7 | import "react-loading-skeleton/dist/skeleton.css";
8 |
9 | export default function Home() {
10 | //Fetch products from strapi
11 | const [results] = useQuery({ query: PRODUCT_QUERY });
12 | const { data, fetching, error } = results;
13 |
14 | //Checks for the data coming in
15 | if (fetching) return Loading...
;
16 | if (error) return Oh no... {error.message}
;
17 | const products = data.products.data;
18 |
19 | return (
20 |
21 |
22 |
Styled Homepage
23 |
24 |
25 |
26 |
27 |
28 |
29 | {fetching && }
30 | {products.map((product) => (
31 |
32 | ))}
33 |
34 |
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/pages/product/[slug].js:
--------------------------------------------------------------------------------
1 | import {
2 | DetailsStyle,
3 | ProductInfo,
4 | Quantity,
5 | Buy,
6 | } from "../../styles/ProductDetails";
7 | import { AiFillPlusCircle, AiFillMinusCircle } from "react-icons/ai";
8 | import { GET_PRODUCT_QUERY } from "../../lib/query";
9 | import { useQuery } from "urql";
10 | import { useRouter } from "next/router";
11 | import { useStateContext } from "../../lib/context";
12 | import toast from "react-hot-toast";
13 | import { useEffect } from "react";
14 |
15 | export default function ProductDetails() {
16 | //Use state
17 | const { increaseQty, decreaseQty, qty, onAdd, setQty } = useStateContext();
18 |
19 | const resetQuantity = () => {
20 | setQty(1);
21 | };
22 | useEffect(() => {
23 | resetQuantity();
24 | }, []);
25 |
26 | //Fetch slug
27 | const { query } = useRouter();
28 | //Fetch Graphql data
29 | const [results] = useQuery({
30 | query: GET_PRODUCT_QUERY,
31 | variables: { slug: query.slug },
32 | });
33 | const { data, fetching, error } = results;
34 | if (fetching) return Loading...
;
35 | if (error) return Oh no... {error.message}
;
36 | //Extract Data
37 | const { title, description, image } = data.products.data[0].attributes;
38 |
39 | //Create Toast
40 | const notify = () => {
41 | toast.success(`${title} added to your cart.`, {
42 | duration: 1500,
43 | });
44 | };
45 |
46 | return (
47 |
48 |
49 |
50 | {title}
51 | {description}
52 |
53 | Quantity
54 |
57 | {qty}
58 |
61 |
62 | {
64 | onAdd(data.products.data[0].attributes, qty);
65 | notify();
66 | }}
67 | >
68 | Add To Cart
69 |
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/pages/profile.js:
--------------------------------------------------------------------------------
1 | import { useRouter } from "next/router";
2 | // Specify Stripe secret api key here
3 | const stripe = require("stripe")(
4 | `${process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY}`
5 | );
6 | import { withPageAuthRequired, getSession } from "@auth0/nextjs-auth0";
7 | import styled from "styled-components";
8 | import formatMoney from "../lib/formatMoney";
9 |
10 | export const getServerSideProps = withPageAuthRequired({
11 | async getServerSideProps(ctx) {
12 | // access the user session
13 | const session = getSession(ctx.req, ctx.res);
14 | const stripeId = session.user[`${process.env.BASE_URL}/stripe_customer_id`];
15 | const paymentIntents = await stripe.paymentIntents.list({
16 | customer: stripeId,
17 | });
18 | return { props: { orders: paymentIntents.data } };
19 | },
20 | });
21 |
22 | export default function Profile({ user, orders }) {
23 | const route = useRouter();
24 |
25 | return (
26 | user && (
27 |
28 |
{user.name}
29 |
{user.email}
30 |
31 | {orders.map((order) => (
32 |
33 |
34 |
Order Number: {order.id}
35 | {formatMoney(order.amount)}
36 |
37 |
38 |
Receipt Email {order.receipt_email}
39 |
40 |
41 | ))}
42 |
43 |
44 |
45 | )
46 | );
47 | }
48 |
49 | const Order = styled.div`
50 | background: white;
51 | margin: 2rem 0rem;
52 | padding: 3rem;
53 | display: flex;
54 | justify-content: space-between;
55 | h1 {
56 | font-size: 1rem;
57 | color: var(--primary);
58 | margin-bottom: 0.5rem;
59 | }
60 | h2 {
61 | font-size: 1rem;
62 | color: var(--secondary);
63 | }
64 | `;
65 |
--------------------------------------------------------------------------------
/pages/success.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import shiba from "../public/shiba-success.png";
3 | import Image from "next/image";
4 | import { useRouter } from "next/router";
5 | const { motion } = require("framer-motion");
6 | // STRIPE_SECRET_KEY
7 | const stripe = require("stripe")(
8 | `${process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY}`
9 | );
10 |
11 | export async function getServerSideProps(params) {
12 | const order = await stripe.checkout.sessions.retrieve(
13 | params.query.session_id,
14 | {
15 | expand: ["line_items"],
16 | }
17 | );
18 |
19 | return { props: { order } };
20 | }
21 |
22 | export default function Success({ order }) {
23 | const route = useRouter();
24 | return (
25 |
26 |
30 | Thank you for your order!
31 | A confirmation email has been sent to
32 | {order.customer_details.email}
33 |
34 |
35 | Adress
36 | {Object.entries(order.customer_details.address).map(
37 | ([key, val]) => (
38 |
39 | {key} : {val}
40 |
41 | )
42 | )}
43 |
44 |
45 | Products
46 | {order.line_items.data.map((item) => (
47 |
48 |
Product: {item.description}
49 |
Quantity: {item.quantity}
50 |
Price: {item.price.unit_amount}
51 |
52 | ))}
53 |
54 |
55 |
56 |
57 |
58 |
59 | );
60 | }
61 |
62 | const Wrapper = styled.div`
63 | margin: 5rem 15rem;
64 | `;
65 |
66 | const Card = styled(motion.div)`
67 | display: flex;
68 | flex-direction: column;
69 | align-items: center;
70 | background: white;
71 | border-radius: 2rem;
72 | padding: 3rem 3rem;
73 |
74 | h1 {
75 | color: var(--primary);
76 | margin-bottom: 1rem;
77 | }
78 | h2 {
79 | color: var(--secondary);
80 | font-weight: 500;
81 | margin-bottom: 0.5rem;
82 | }
83 | button {
84 | background: var(--primary);
85 | color: white;
86 | font-weight: 500;
87 | font-size: 1.2rem;
88 | padding: 1rem 2rem;
89 | margin-top: 2rem;
90 | cursor: pointer;
91 | }
92 | `;
93 | const Address = styled.div`
94 | font-size: 1rem;
95 | width: 100%;
96 | `;
97 | const OrderInfo = styled.div`
98 | font-size: 1rem;
99 | width: 100%;
100 | div {
101 | padding-bottom: 1rem;
102 | }
103 | `;
104 | const InfoWrapper = styled.div`
105 | margin-top: 2rem;
106 | display: flex;
107 | `;
108 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developedbyed/styled-frontend/661e9351b9bbcaae7760cd388d21d9dc5f270316/public/favicon.ico
--------------------------------------------------------------------------------
/public/shiba-success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/developedbyed/styled-frontend/661e9351b9bbcaae7760cd388d21d9dc5f270316/public/shiba-success.png
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/CartStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | //Animation
3 | const { motion } = require("framer-motion");
4 |
5 | export const CartWrapper = styled(motion.div)`
6 | position: fixed;
7 | right: 0;
8 | top: 0;
9 | height: 100vh;
10 | width: 100%;
11 | background: rgba(0, 0, 0, 0.4);
12 | z-index: 100;
13 | display: flex;
14 | justify-content: flex-end;
15 | `;
16 |
17 | export const CartStyle = styled(motion.div)`
18 | width: 30%;
19 | background: #f1f1f1;
20 | padding: 2rem 5rem;
21 | overflow-y: scroll;
22 | position: relative;
23 | `;
24 |
25 | export const Card = styled(motion.div)`
26 | display: flex;
27 | align-items: center;
28 | justify-content: space-between;
29 | border-radius: 1rem;
30 | overflow: hidden;
31 | background: white;
32 | padding: 2rem;
33 | margin: 2rem 0rem;
34 |
35 | img {
36 | width: 8rem;
37 | }
38 | `;
39 |
40 | export const CardInfo = styled(motion.div)`
41 | width: 50%;
42 | div {
43 | display: flex;
44 | justify-content: space-between;
45 | }
46 | `;
47 |
48 | export const EmptyStyle = styled(motion.div)`
49 | /* For the empty cart */
50 | position: absolute;
51 | top: 0;
52 | /* */
53 | display: flex;
54 | flex-direction: column;
55 | align-items: center;
56 | justify-content: center;
57 | height: 100%;
58 | width: 80%;
59 | svg {
60 | font-size: 8rem;
61 | color: var(--secondary);
62 | }
63 | `;
64 |
65 | export const Checkout = styled(motion.div)`
66 | button {
67 | background: var(--primary);
68 | padding: 1rem 2rem;
69 | width: 100%;
70 | color: white;
71 | margin-top: 2rem;
72 | cursor: pointer;
73 | }
74 | `;
75 |
--------------------------------------------------------------------------------
/styles/Gallery.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const Gallery = styled.div`
4 | display: grid;
5 | grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
6 | grid-gap: 2rem;
7 | `;
8 |
--------------------------------------------------------------------------------
/styles/NavStyles.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const NavStyles = styled.nav`
4 | min-height: 15vh;
5 | display: flex;
6 | justify-content: space-between;
7 | align-items: center;
8 | font-size: 1rem;
9 | color: #303030;
10 |
11 | a {
12 | font-size: 1.2rem;
13 | }
14 | `;
15 |
16 | export const NavItems = styled.div`
17 | display: flex;
18 | align-items: center;
19 | justify-content: space-around;
20 | div {
21 | margin-left: 3rem;
22 | position: relative;
23 | display: flex;
24 | flex-direction: column;
25 | align-items: center;
26 | cursor: pointer;
27 | }
28 | h3 {
29 | font-size: 0.75rem;
30 | padding: 0.25rem;
31 | }
32 | svg {
33 | font-size: 1.5rem;
34 | }
35 | span {
36 | background: #ff2626;
37 | color: white;
38 | width: 1.2rem;
39 | height: 1.2rem;
40 | border-radius: 50%;
41 | display: flex;
42 | justify-content: center;
43 | align-items: center;
44 | font-size: 0.75rem;
45 | position: absolute;
46 | right: -10%;
47 | top: -20%;
48 | font-weight: 700;
49 | pointer-events: none;
50 | }
51 | `;
52 |
--------------------------------------------------------------------------------
/styles/ProductDetails.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const DetailsStyle = styled.div`
4 | display: flex;
5 | justify-content: space-between;
6 | margin-top: 5rem;
7 | img {
8 | width: 40%;
9 | }
10 | `;
11 |
12 | export const ProductInfo = styled.div`
13 | width: 40%;
14 | button {
15 | font-size: 1rem;
16 | font-weight: medium;
17 | padding: 0.5rem 1rem;
18 | cursor: pointer;
19 | }
20 | `;
21 |
22 | export const Quantity = styled.div`
23 | display: flex;
24 | align-items: center;
25 | margin: 1rem 0rem;
26 |
27 | button {
28 | background: transparent;
29 | border: none;
30 | display: flex;
31 | font-size: 1.5rem;
32 | }
33 | p {
34 | width: 1rem;
35 | text-align: center;
36 | }
37 | span {
38 | color: var(--secondary);
39 | }
40 |
41 | svg {
42 | color: #494949;
43 | }
44 | `;
45 |
46 | export const Buy = styled.button`
47 | width: 100%;
48 | background: var(--primary);
49 | color: white;
50 | font-weight: 500;
51 | `;
52 |
--------------------------------------------------------------------------------
/styles/ProductStyle.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | export const ProductStyles = styled.div`
4 | background: white;
5 | position: relative;
6 | display: flex;
7 | flex-direction: column;
8 | padding: 1.5rem;
9 | cursor: pointer;
10 | img {
11 | width: 100%;
12 | object-fit: cover;
13 | }
14 | h2 {
15 | padding: 0.5rem 0rem;
16 | }
17 | `;
18 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");
2 |
3 | html,
4 | body {
5 | padding: 0;
6 | margin: 0;
7 | font-family: "Inter", sans-serif;
8 | margin: 0rem 10%;
9 | background: #f1f1f1;
10 | --primary: #2d2d2d;
11 | --secondary: #535353;
12 | }
13 |
14 | h1 {
15 | font-size: 2rem;
16 | color: var(--primary);
17 | padding: 2rem 0rem;
18 | }
19 |
20 | h2 {
21 | font-size: 1.2rem;
22 | color: var(--primary);
23 | padding: 0.5rem 0rem;
24 | }
25 | h3 {
26 | font-size: 1rem;
27 | color: var(--secondary);
28 | }
29 |
30 | a {
31 | color: inherit;
32 | text-decoration: none;
33 | }
34 |
35 | * {
36 | box-sizing: border-box;
37 | padding: 0;
38 | margin: 0;
39 | }
40 |
41 | p {
42 | line-height: 150%;
43 | color: var(--secondary);
44 | }
45 |
--------------------------------------------------------------------------------