├── .gitignore ├── README.md ├── components ├── BuyButton.js ├── Footer.js └── Header.js ├── context └── AuthContext.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── account.js ├── index.js ├── login.js ├── products │ └── [slug].js └── success.js ├── products.json ├── public ├── NextJS.jpg ├── favicon.ico ├── user_avatar.png └── vercel.svg ├── styles ├── BuyButton.module.css ├── Header.module.css ├── Home.module.css ├── Login.module.css └── globals.css └── utils ├── format.js └── urls.js /.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 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Next Magic Stripe Strapi Frontend 2 | 3 | ### Companion Video 4 | https://www.youtube.com/watch?v=385cpCpGRC0 5 | 6 | 7 | Sponsored by Magic https://magic.link/ 8 | 9 | NextJS Frontend with Magic for Authentication, Stripe for Checkout Payment, Strapi for Product, Orders and User management 10 | 11 | Check /utils/urls for ENV Variables setup 12 | 13 | Backend at: 14 | https://github.com/GalloDaSballo/Next-Ecommerce-Backend 15 | -------------------------------------------------------------------------------- /components/BuyButton.js: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router' 2 | import { loadStripe } from '@stripe/stripe-js' 3 | import { API_URL, STRIPE_PK } from '../utils/urls' 4 | import styles from '../styles/BuyButton.module.css' 5 | import { useContext } from "react"; 6 | import AuthContext from "../context/AuthContext"; 7 | 8 | const stripePromise = loadStripe(STRIPE_PK) 9 | 10 | export default function BuyButton ({ product }) { 11 | const { user, getToken } = useContext(AuthContext); 12 | 13 | const router = useRouter() 14 | 15 | const handleBuy = async (e) => { 16 | const stripe = await stripePromise 17 | const token = await getToken() 18 | console.log("handleBuy token", token) 19 | e.preventDefault() 20 | const res = await fetch(`${API_URL}/orders/`, { 21 | method: 'POST', 22 | body: JSON.stringify({product}), 23 | headers: { 24 | 'Content-type': 'application/json', 25 | 'Authorization': `Bearer ${token}` 26 | } 27 | }) 28 | const session = await res.json() 29 | console.log("session", session) 30 | 31 | const result = await stripe.redirectToCheckout({ 32 | sessionId: session.id, 33 | }); 34 | } 35 | 36 | const redirectToLogin = async () => { 37 | router.push('/login') 38 | } 39 | 40 | return( 41 | <> 42 | {user && 43 | 44 | } 45 | {!user && 46 | 47 | } 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /components/Footer.js: -------------------------------------------------------------------------------- 1 | export default () => ( 2 | 17 | ) -------------------------------------------------------------------------------- /components/Header.js: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import Link from 'next/link' 3 | import { useRouter } from 'next/router' 4 | import AuthContext from "../context/AuthContext" 5 | 6 | 7 | import styles from '../styles/Header.module.css' 8 | 9 | export default () => { 10 | 11 | const router = useRouter() 12 | const isHome = router.pathname === "/" 13 | 14 | const { user } = useContext(AuthContext); 15 | 16 | const goBack = (event) => { 17 | event.preventDefault() 18 | router.back() 19 | } 20 | 21 | 22 | 23 | return ( 24 |
25 | {!isHome && 26 |
27 | {"<"} Back 28 |
29 | } 30 |
31 | 32 | 33 |

34 | FullStack E-Commerce with NextJS, Magic, Strapi and Stripe 35 |

36 |
37 | 38 |
39 | 40 |
41 | {user ? ( 42 | 43 | 44 | {user.email} 45 | 46 | 47 | ) : ( 48 | 49 | Log In 50 | 51 | )} 52 |
53 | 54 |
55 | ) 56 | } -------------------------------------------------------------------------------- /context/AuthContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useEffect } from "react"; 2 | import { Magic } from "magic-sdk"; 3 | import { MAGIC_PUBLIC_KEY } from "../utils/urls"; 4 | import { useRouter } from "next/router"; 5 | 6 | const AuthContext = createContext(); 7 | 8 | let magic 9 | 10 | export const AuthProvider = (props) => { 11 | const [user, setUser] = useState(null); 12 | const router = useRouter(); 13 | 14 | /** 15 | * Log the user in 16 | * @param {string} email 17 | */ 18 | const loginUser = async (email) => { 19 | try { 20 | await magic.auth.loginWithMagicLink({ email }); 21 | setUser({ email }); 22 | router.push("/"); 23 | } catch (err) { 24 | console.log(err); 25 | } 26 | }; 27 | 28 | /** 29 | * Log the user out 30 | */ 31 | const logoutUser = async () => { 32 | try { 33 | await magic.user.logout(); 34 | setUser(null); 35 | router.push("/"); 36 | } catch (err) { 37 | console.log(err); 38 | } 39 | }; 40 | 41 | /** 42 | * If user is logged in, get data and display it 43 | */ 44 | const checkUserLoggedIn = async () => { 45 | try { 46 | const isLoggedIn = await magic.user.isLoggedIn(); 47 | 48 | if (isLoggedIn) { 49 | const { email } = await magic.user.getMetadata(); 50 | setUser({ email }); 51 | //Add this just for test 52 | const token = await getToken() 53 | console.log("checkUserLoggedIn token", token) 54 | } 55 | } catch (err) { 56 | console.log(err); 57 | } 58 | }; 59 | 60 | /** 61 | * Retrieve Magic Issued Bearer Token 62 | * This allows User to make authenticated requests 63 | */ 64 | const getToken = async () => { 65 | try{ 66 | const token = await magic.user.getIdToken() 67 | return token 68 | } catch (err) { 69 | console.log(err) 70 | } 71 | } 72 | 73 | /** 74 | * Reload user login on app refresh 75 | */ 76 | useEffect(() => { 77 | magic = new Magic(MAGIC_PUBLIC_KEY) 78 | 79 | checkUserLoggedIn() 80 | }, []); 81 | 82 | return ( 83 | 84 | {props.children} 85 | 86 | ); 87 | }; 88 | 89 | 90 | export default AuthContext -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecommerce-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "@stripe/stripe-js": "^1.11.0", 12 | "magic-sdk": "^3.0.1", 13 | "next": "10.0.1", 14 | "react": "17.0.1", 15 | "react-dom": "17.0.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import Header from '../components/Header' 3 | import Footer from '../components/Footer' 4 | 5 | import { AuthProvider } from '../context/AuthContext' 6 | 7 | function MyApp({ Component, pageProps }) { 8 | return ( 9 | 10 | 11 |
12 | 13 |