├── .env ├── .gitignore ├── README.md ├── components ├── .env ├── Box.js ├── Boxes.js ├── Boxx.jsx ├── CWload-new.js ├── CWload.js ├── CWload2.js ├── Canvas3.jsx ├── Cart.tsx ├── CartSummary.tsx ├── CheckoutForm.tsx ├── ClearCart.tsx ├── ConnectWallet.js ├── CustomDonationInput.tsx ├── Draggable.jsx ├── ElementsForm.tsx ├── Floor.jsx ├── Layout.tsx ├── LightBulb.jsx ├── Modal │ ├── index.js │ └── modal.css ├── OrbitControls.jsx ├── PrintObject.tsx ├── Products.tsx ├── StripeTestCards.tsx ├── firebase │ └── client.tsx └── thirdwebsdk.js ├── config └── index.ts ├── contexts ├── Address │ ├── index.jsx │ └── reducer.js └── web3 │ └── index.js ├── data └── products.ts ├── lib └── firebase.js ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── 404.tsx ├── _app.tsx ├── _app.xjsx ├── admin │ ├── edit │ │ ├── [id].js │ │ └── index.js │ └── post.js ├── api │ ├── checkout_sessions │ │ ├── [id].ts │ │ ├── cart.ts │ │ └── index.ts │ ├── payment_intents │ │ └── index.ts │ ├── user.js │ └── webhooks │ │ └── index.ts ├── boxes.tsx ├── edit.js ├── faq.tsx ├── index.tsx ├── post.js ├── result.tsx ├── shop.tsx ├── thanks.tsx ├── web3.xjs ├── whitelist-thanks.tsx └── whitelist.tsx ├── public ├── Archidao_wearable.js ├── Test.js ├── archidao.png ├── archidao_wearable.bin ├── archidao_wearable.glb ├── archidao_wearable.gltf ├── archidao_wearable_2.gltf ├── black_ready.jpg ├── checkout-one-time-payments.svg ├── checkout_demo.gif ├── draw-with-compass.svg ├── elements-card-payment.svg ├── elements_demo.gif ├── logo.png ├── male_2.glb ├── male_2.gltf ├── nft_.png ├── preview.png ├── shopping_cart_demo.gif ├── social_card.png ├── test.gltf └── use-shopping-cart.png ├── store ├── context.js ├── globalStateProvider.js └── useGlobalState.js ├── styles.css ├── styles └── Home.module.css ├── tsconfig.json ├── utils ├── api-helpers.ts ├── get-stripejs.ts ├── index.js └── stripe-helpers.ts ├── with-stripe-typescript-app.zip └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | # Stripe keys 2 | # https://dashboard.stripe.com/apikeys 3 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_51JEY9A4DPeJl1lYzNLvnJS2E6zQHyIXOvwYXD9D34apyGU6xFrErlR9QHTA1z2u8OwCdd31pL1Q0TgltNfLTBGbr00THgtSgJY 4 | STRIPE_SECRET_KEY=sk_test_51JEY9A4DPeJl1lYzXuTamAP7fSgy7kg4KJLNVozrRGp91wWbSso36YKmYF2D0WBtoBgzewPjo1QWMCa3atUKlA4y00wEDGWrlW 5 | STRIPE_PAYMENT_DESCRIPTION='ArchiDAO NFT' 6 | # https://stripe.com/docs/webhooks/signatures 7 | STRIPE_WEBHOOK_SECRET=whsec_1234 8 | INFURA_ID = 10499f16823b4f2c9ef9ca2536c6fab7 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # bad code 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | serviceAccountKey.json 35 | 36 | # vercel 37 | .vercel 38 | 39 | .vercel 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next Stripe Web3 Three JS 2 | 3 | A Next JS project that pulls together all the libraries needed for a slick website with web3 and web2 payment integrations. Not yet slick but we're getting there. 4 | 5 | `yarn dev` should get you going. 6 | 7 | ## Tasks 8 | In progress: 9 | - database (gcp + orbitdb maybe) @jb4earth 10 | 11 | Help wanted: 12 | - CSS fixes 13 | - Three JS updates 14 | 15 | License: MIT 16 | -------------------------------------------------------------------------------- /components/.env: -------------------------------------------------------------------------------- 1 | ### remove the '#' to use the environment variables 2 | export PRIVATE_KEY=31390d749ec2eef68713a7b18f9141655f936aff74193b22cb4394c21a8da4a4 3 | export WEB3_INFURA_PROJECT_ID=0ad6386686934f6585cd8eefc9739c04 4 | 5 | 6 | ### Optional - don't uncomment if you don't know what they do! 7 | # export ETHERSCAN_TOKEN=asdfasdfasdf 8 | export IPFS_URL=http://127.0.0.1:5001 9 | export UPLOAD_IPFS=true 10 | # export PINATA_API_KEY='asdfasdfs' 11 | # export PINATA_API_SECRET='asdfasdfsdf' 12 | -------------------------------------------------------------------------------- /components/Box.js: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | import { useFrame, useThree } from '@react-three/fiber'; 3 | import { Box as NativeBox } from '@react-three/drei'; 4 | 5 | export default function Box(props) { 6 | const mesh = useRef(); 7 | const { viewport } = useThree(); 8 | 9 | const [hovered, setHover] = useState(false); 10 | const [active, setActive] = useState(false); 11 | 12 | useFrame(({ mouse }) => { 13 | mesh.current.rotation.x = mesh.current.rotation.y += 0.01; 14 | const x = (mouse.x * viewport.width) / 2; 15 | const y = (mouse.y * viewport.height) / 2; 16 | mesh.current.position.set(x, y, 0); 17 | }); 18 | 19 | return ( 20 | setActive(!active)} 26 | onPointerOver={() => setHover(true)} 27 | onPointerOut={() => setHover(false)} 28 | > 29 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /components/Boxes.js: -------------------------------------------------------------------------------- 1 | import { Canvas } from '@react-three/fiber' 2 | import { OrbitControls } from '@react-three/drei' 3 | import Box from './Box' 4 | 5 | const Boxes = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | 18 | export default Boxes 19 | -------------------------------------------------------------------------------- /components/Boxx.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLoader } from "@react-three/fiber"; 3 | import { TextureLoader } from "three"; 4 | 5 | function Box(props) { 6 | const texture = useLoader(TextureLoader, "/texture.jpg"); 7 | return ( 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | export default Box; 16 | -------------------------------------------------------------------------------- /components/CWload-new.js: -------------------------------------------------------------------------------- 1 | import { 2 | 3 | useAddress, 4 | 5 | } from '@thirdweb-dev/react'; 6 | 7 | 8 | export const CWload = () => { 9 | 10 | const address = useAddress(); 11 | 12 | return address 13 | }; 14 | -------------------------------------------------------------------------------- /components/CWload.js: -------------------------------------------------------------------------------- 1 | import { 2 | 3 | useAddress, 4 | 5 | } from '@thirdweb-dev/react'; 6 | import { NextPage } from 'next' 7 | import Link from 'next/link' 8 | import Layout from '../components/Layout' 9 | import Head from "next/head"; 10 | 11 | 12 | import { setDoc,doc,collection, getFirestore,updateDoc, arrayUnion } from '@firebase/firestore'; 13 | import { AddressContext } from "../contexts/Address" 14 | import { useState } from 'react'; 15 | import { firestore } from '../components/firebase/client'; 16 | 17 | import React from "react" 18 | 19 | 20 | 21 | 22 | export const CWload = (context) => { 23 | 24 | const address = useAddress(); 25 | if (address) { 26 | const timestamp = Date.now().toString(); 27 | console.log(timestamp) 28 | // updateDoc(doc(firestore, "crypto_logins", String(address)), { 29 | // [context]: arrayUnion(String(timestamp)) }).catch(()=>{ setDoc(doc(firestore, "crypto_logins", String(address)), { 30 | // [context]: arrayUnion(String(timestamp)) });}) 31 | 32 | 33 | } else {} 34 | 35 | 36 | return address 37 | }; 38 | -------------------------------------------------------------------------------- /components/CWload2.js: -------------------------------------------------------------------------------- 1 | import { 2 | 3 | useAddress, 4 | 5 | } from '@thirdweb-dev/react'; 6 | import { NextPage } from 'next' 7 | import Link from 'next/link' 8 | import Layout from '../components/Layout' 9 | import Head from "next/head"; 10 | 11 | 12 | import { setDoc,doc,collection, getFirestore,updateDoc, arrayUnion } from '@firebase/firestore'; 13 | import { AddressContext } from "../contexts/Address" 14 | import { useState } from 'react'; 15 | import { firestore } from '../components/firebase/client'; 16 | 17 | import React from "react" 18 | 19 | 20 | 21 | 22 | export const CWload = (context) => { 23 | 24 | const address = useAddress(); 25 | if (address) { 26 | const timestamp = Date.now().toString(); 27 | console.log(timestamp) 28 | updateDoc(doc(firestore, "crypto_logins", String(address)), { 29 | [context]: arrayUnion(String(timestamp)) }).catch(()=>{ setDoc(doc(firestore, "crypto_logins", String(address)), { 30 | [context]: arrayUnion(String(timestamp)) });}) 31 | 32 | 33 | } else {} 34 | 35 | 36 | return address 37 | }; 38 | -------------------------------------------------------------------------------- /components/Canvas3.jsx: -------------------------------------------------------------------------------- 1 | import css from "../styles/Home.module.css"; 2 | import { useLoader, useFrame,useThree } from "@react-three/fiber"; 3 | import { Canvas } from "@react-three/fiber"; 4 | import Box from "./Boxx"; 5 | import OrbitControls from "./OrbitControls"; 6 | import LightBulb from "./LightBulb"; 7 | import Floor from "./Floor"; 8 | import Draggable from "./Draggable"; 9 | import {Suspense} from "react"; 10 | import Model from '../public/Archidao_wearable.js' 11 | import React, {useState} from "react"; 12 | import { useMouse } from "rooks"; 13 | function MyRotatingBox() { 14 | const ref = React.useRef() 15 | 16 | const myMesh = React.useRef(); 17 | const mouseLightMesh = React.useRef(); 18 | 19 | const { viewport } = useThree(); 20 | 21 | const [hovered, setHover] = useState(false); 22 | const [active, setActive] = useState(false); 23 | const [intensity, setIntensity] = useState(false); 24 | const { x, y } = useMouse(); 25 | useFrame(({ clock }) => { 26 | const a = clock.getElapsedTime(); 27 | myMesh.current.rotation.y = a*-.5; 28 | 29 | // mesh.current.rotation.x = mesh.current.rotation.y += 0.01; 30 | const x2 = (x / viewport.width); 31 | const y2 = (y / viewport.height); 32 | // console.log(docY) 33 | // console.log(posX) 34 | // console.log(elX) 35 | mouseLightMesh.current.position.set(x2*3-60, -(-y2*2.5+40), 6); 36 | }); 37 | return ( 38 | <> 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ); 56 | } 57 | const light_info = [ 58 | {position: [-20,0,0],color:'green',intensity:.5}, 59 | {position: [-10,5,-10],color:'purple',intensity:.5}, 60 | {position: [20,5,10],color:'blue',intensity:.5}, 61 | {position: [0,10,10],color:'pink',intensity:.5}, 62 | ] 63 | 64 | 65 | export default function Canvas3() { 66 | return ( 67 |
68 | 75 | {light_info.map((lightx) => ( 76 | 77 | 78 | 79 | ))} 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /components/Cart.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react' 2 | import { CartProvider } from 'use-shopping-cart/react' 3 | import * as config from '../config' 4 | 5 | const Cart = ({ children }: { children: ReactNode }) => ( 6 | 11 | <>{children} 12 | 13 | ) 14 | 15 | export default Cart 16 | -------------------------------------------------------------------------------- /components/CartSummary.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useCallback } from 'react' 2 | import { 3 | useNetwork, 4 | useAddress, 5 | } from '@thirdweb-dev/react'; 6 | import Web3 from 'web3'; 7 | import StripeTestCards from '../components/StripeTestCards' 8 | import { CWload } from '../components/CWload'; 9 | import { AddressContext } from "../contexts/Address" 10 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 11 | import { useShoppingCart } from 'use-shopping-cart/react' 12 | import { fetchPostJSON } from '../utils/api-helpers' 13 | const CartSummary = () => { 14 | const [loading, setLoading] = useState(false) 15 | const [result, setResult] = useState(true) 16 | const [cartEmpty, setCartEmpty] = useState(true) 17 | const [errorMessage, setErrorMessage] = useState('') 18 | const { 19 | formattedTotalPrice, 20 | cartCount, 21 | clearCart, 22 | cartDetails, 23 | redirectToCheckout, 24 | } = useShoppingCart() 25 | 26 | useEffect(() => setCartEmpty(!cartCount), [cartCount]) 27 | // useEffect(() => setResult(!cartCount), [cartCount]) 28 | 29 | const [ state, dispatch ] = React.useContext(AddressContext) 30 | console.log(state) 31 | console.log(state.address) 32 | console.log('cartsumm.jsx') 33 | 34 | 35 | const handleCheckout: React.FormEventHandler = async ( 36 | event 37 | ) => { 38 | event.preventDefault() 39 | setLoading(true) 40 | setErrorMessage('') 41 | 42 | console.log(state.address) 43 | console.log('got hrere') 44 | 45 | 46 | const response = await fetchPostJSON( 47 | '/api/checkout_sessions/cart', 48 | cartDetails 49 | ) 50 | 51 | if (response.statusCode > 399) { 52 | console.error(response.message) 53 | setErrorMessage(response.message) 54 | setLoading(false) 55 | return 56 | } 57 | 58 | redirectToCheckout({ sessionId: response.id }) 59 | } 60 | console.log(state) 61 | console.log(state.active) 62 | console.log('CS pre retren') 63 | console.log(cartEmpty) 64 | if (CWload('CartSummary.tsx')){ return ( 65 |
66 | {errorMessage ? ( 67 |

Error: {errorMessage}

68 | ) : null} 69 | {/* This is where we'll render our cart */} 70 | 71 | {/* Redirects the user to Stripe */} 72 |
73 |
74 |

75 | Quantity: {cartCount} 76 |

77 |
78 |

79 | Totals: {formattedTotalPrice} 80 |

81 |
82 |
83 | 90 | 97 | 105 |
106 | 107 | 108 |
109 |
110 | )}else{return(

please connect wallet

)} 111 | 112 | } 113 | 114 | export default CartSummary 115 | -------------------------------------------------------------------------------- /components/CheckoutForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | 3 | import CustomDonationInput from '../components/CustomDonationInput' 4 | import StripeTestCards from '../components/StripeTestCards' 5 | 6 | import getStripe from '../utils/get-stripejs' 7 | import { fetchPostJSON } from '../utils/api-helpers' 8 | import { formatAmountForDisplay } from '../utils/stripe-helpers' 9 | import * as config from '../config' 10 | 11 | const CheckoutForm = () => { 12 | const [loading, setLoading] = useState(false) 13 | const [input, setInput] = useState({ 14 | customDonation: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP), 15 | }) 16 | 17 | const handleInputChange: React.ChangeEventHandler = (e) => 18 | setInput({ 19 | ...input, 20 | [e.currentTarget.name]: e.currentTarget.value, 21 | }) 22 | 23 | const handleSubmit: React.FormEventHandler = async (e) => { 24 | e.preventDefault() 25 | setLoading(true) 26 | // Create a Checkout Session. 27 | const response = await fetchPostJSON('/api/checkout_sessions', { 28 | amount: input.customDonation, 29 | }) 30 | 31 | if (response.statusCode === 500) { 32 | console.error(response.message) 33 | return 34 | } 35 | 36 | // Redirect to Checkout. 37 | const stripe = await getStripe() 38 | const { error } = await stripe!.redirectToCheckout({ 39 | // Make the id field from the Checkout Session creation API response 40 | // available to this file, so you can provide it as parameter here 41 | // instead of the {{CHECKOUT_SESSION_ID}} placeholder. 42 | sessionId: response.id, 43 | }) 44 | // If `redirectToCheckout` fails due to a browser or network 45 | // error, display the localized error message to your customer 46 | // using `error.message`. 47 | console.warn(error.message) 48 | setLoading(false) 49 | } 50 | 51 | return ( 52 |
53 | 63 | 64 | 71 | 72 | ) 73 | } 74 | 75 | export default CheckoutForm 76 | -------------------------------------------------------------------------------- /components/ClearCart.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | import { useShoppingCart } from 'use-shopping-cart/react' 3 | 4 | export default function ClearCart() { 5 | const { clearCart } = useShoppingCart() 6 | 7 | useEffect(() => clearCart(), [clearCart]) 8 | 9 | return

Cart cleared.

10 | } 11 | -------------------------------------------------------------------------------- /components/ConnectWallet.js: -------------------------------------------------------------------------------- 1 | import { 2 | useMetamask, 3 | useWalletConnect, 4 | useCoinbaseWallet, 5 | useNetwork, 6 | useAddress, 7 | useDisconnect, 8 | } from '@thirdweb-dev/react'; 9 | import { AddressContext } from "../contexts/Address" 10 | import React from "react" 11 | // import {store, useGlobalState} from 'state-pool'; 12 | // 13 | // store.setState("user",{address: '', transaction: '', active:''}); 14 | 15 | 16 | // import { Web3Context } from "../contexts/web3" 17 | 18 | export const ConnectWallet = () => { 19 | const connectWithCoinbaseWallet = useCoinbaseWallet(); 20 | const connectWithMetamask = useMetamask(); 21 | const connectWithWalletConnect = useWalletConnect(); 22 | const disconnectWallet = useDisconnect(); 23 | // const address = 24 | const network = useNetwork(); 25 | 26 | 27 | 28 | const [ state, setState ] = React.useContext(AddressContext) 29 | 30 | const hold_address = useAddress(); 31 | 32 | // const [user, setUser, updateUser] = useGlobalState("user"); 33 | // updateUser(function(user){ 34 | // user.address = state.address 35 | // }) 36 | // console.log(user.address) 37 | // console.log(state.address) 38 | // console.log('CW first.js') 39 | 40 | 41 | 42 | 43 | const disconnectWalletX = () => { 44 | // state.address = false 45 | setState({address: false,active:false}) 46 | state.active = false 47 | state.address = false 48 | 49 | disconnectWallet() 50 | console.log(state) 51 | console.log('ConnectWallet.js disconnect') 52 | } 53 | 54 | const walletConnected = () => { 55 | if (state.active === false) { 56 | setState({address: hold_address, active: true}) 57 | // dispatch({address: hold_address}) 58 | // dispatch({active:true}) 59 | state.address = hold_address 60 | state.active = true 61 | console.log(state) 62 | console.log('ConnectWallet.js connect') 63 | } 64 | // console.log(state.address) 65 | // console.log(state) 66 | // const {addressx,setAddress} = React.useContext(Web3Context) 67 | // if (state.address !== addressx){setAddress(state.address)} 68 | // else {console.log('why render?')} 69 | // console.log('pre-button render') 70 | } 71 | // If a wallet is connected, show address, chainId and disconnect button 72 | if (hold_address) { 73 | walletConnected() 74 | return ( 75 | <> 76 | 77 | 78 | ); 79 | } 80 | 81 | // If no wallet is connected, show connect wallet options 82 | return ( 83 | <> 84 | 87 | 88 | 91 | 92 | ); 93 | }; 94 | -------------------------------------------------------------------------------- /components/CustomDonationInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { formatAmountForDisplay } from '../utils/stripe-helpers' 3 | 4 | type Props = { 5 | name: string 6 | value: number 7 | min: number 8 | max: number 9 | currency: string 10 | step: number 11 | onChange: (e: React.ChangeEvent) => void 12 | className?: string 13 | } 14 | 15 | const CustomDonationInput = ({ 16 | name, 17 | value, 18 | min, 19 | max, 20 | currency, 21 | step, 22 | onChange, 23 | className, 24 | }: Props) => ( 25 | 48 | ) 49 | 50 | export default CustomDonationInput 51 | -------------------------------------------------------------------------------- /components/Draggable.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import { extend, useThree } from "@react-three/fiber"; 3 | import { DragControls } from "three/examples/jsm/controls/DragControls"; 4 | 5 | extend({ DragControls }); 6 | 7 | function Draggable(props) { 8 | const groupRef = useRef(); 9 | const controlsRef = useRef(); 10 | const [objects, setObjects] = useState(); 11 | const { camera, gl, scene } = useThree(); 12 | useEffect(() => { 13 | setObjects(groupRef.current.children); 14 | }, [groupRef]); 15 | 16 | useEffect(() => { 17 | controlsRef.current.addEventListener("hoveron", () => { 18 | scene.orbitControls.enabled = false; 19 | }); 20 | controlsRef.current.addEventListener("hoveroff", () => { 21 | scene.orbitControls.enabled = true; 22 | }); 23 | }, [objects]); 24 | return ( 25 | 26 | 27 | {props.children} 28 | 29 | ); 30 | } 31 | 32 | export default Draggable; 33 | -------------------------------------------------------------------------------- /components/ElementsForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, FC } from 'react' 2 | 3 | import CustomDonationInput from '../components/CustomDonationInput' 4 | import StripeTestCards from '../components/StripeTestCards' 5 | import PrintObject from '../components/PrintObject' 6 | 7 | import { fetchPostJSON } from '../utils/api-helpers' 8 | import { 9 | formatAmountForDisplay, 10 | formatAmountFromStripe, 11 | } from '../utils/stripe-helpers' 12 | import * as config from '../config' 13 | 14 | import { useStripe, useElements, PaymentElement } from '@stripe/react-stripe-js' 15 | import { PaymentIntent } from '@stripe/stripe-js' 16 | 17 | const ElementsForm: FC<{ 18 | paymentIntent?: PaymentIntent | null 19 | }> = ({ paymentIntent = null }) => { 20 | const defaultAmout = paymentIntent 21 | ? formatAmountFromStripe(paymentIntent.amount, paymentIntent.currency) 22 | : Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP) 23 | const [input, setInput] = useState({ 24 | customDonation: defaultAmout, 25 | cardholderName: '', 26 | }) 27 | const [paymentType, setPaymentType] = useState('') 28 | const [payment, setPayment] = useState({ status: 'initial' }) 29 | const [errorMessage, setErrorMessage] = useState('') 30 | const stripe = useStripe() 31 | const elements = useElements() 32 | 33 | const PaymentStatus = ({ status }: { status: string }) => { 34 | switch (status) { 35 | case 'processing': 36 | case 'requires_payment_method': 37 | case 'requires_confirmation': 38 | return

Processing...

39 | 40 | case 'requires_action': 41 | return

Authenticating...

42 | 43 | case 'succeeded': 44 | return

Payment Succeeded 🥳

45 | 46 | case 'error': 47 | return ( 48 | <> 49 |

Error 😭

50 |

{errorMessage}

51 | 52 | ) 53 | 54 | default: 55 | return null 56 | } 57 | } 58 | 59 | const handleInputChange: React.ChangeEventHandler = (e) => 60 | setInput({ 61 | ...input, 62 | [e.currentTarget.name]: e.currentTarget.value, 63 | }) 64 | 65 | const handleSubmit: React.FormEventHandler = async (e) => { 66 | e.preventDefault() 67 | // Abort if form isn't valid 68 | if (!e.currentTarget.reportValidity()) return 69 | if (!elements) return 70 | setPayment({ status: 'processing' }) 71 | 72 | // Create a PaymentIntent with the specified amount. 73 | const response = await fetchPostJSON('/api/payment_intents', { 74 | amount: input.customDonation, 75 | payment_intent_id: paymentIntent?.id, 76 | }) 77 | setPayment(response) 78 | 79 | if (response.statusCode === 500) { 80 | setPayment({ status: 'error' }) 81 | setErrorMessage(response.message) 82 | return 83 | } 84 | 85 | // Use your card Element with other Stripe.js APIs 86 | const { error } = await stripe!.confirmPayment({ 87 | elements, 88 | confirmParams: { 89 | return_url: 'http://localhost:3000/donate-with-elements', 90 | payment_method_data: { 91 | billing_details: { 92 | name: input.cardholderName, 93 | }, 94 | }, 95 | }, 96 | }) 97 | 98 | if (error) { 99 | setPayment({ status: 'error' }) 100 | setErrorMessage(error.message ?? 'An unknown error occurred') 101 | } else if (paymentIntent) { 102 | setPayment(paymentIntent) 103 | } 104 | } 105 | 106 | return ( 107 | <> 108 |
109 | 119 | 120 |
121 | Your payment details: 122 | {paymentType === 'card' ? ( 123 | 131 | ) : null} 132 |
133 | { 135 | setPaymentType(e.value.type) 136 | }} 137 | /> 138 |
139 |
140 | 150 | 151 | 152 | 153 | 154 | ) 155 | } 156 | 157 | export default ElementsForm 158 | -------------------------------------------------------------------------------- /components/Floor.jsx: -------------------------------------------------------------------------------- 1 | //components/Floor.jsx 2 | 3 | import React from "react"; 4 | 5 | function Floor(props) { 6 | return ( 7 | 8 | 9 | 10 | 11 | ); 12 | } 13 | 14 | export default Floor; 15 | -------------------------------------------------------------------------------- /components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode,useContext } from 'react' 2 | import Head from 'next/head' 3 | import Link from 'next/link' 4 | import { library } from "@fortawesome/fontawesome-svg-core"; 5 | import Canvas3 from './Canvas3' 6 | import { fab,faTwitter,faDiscord,faGithub } from '@fortawesome/free-brands-svg-icons' 7 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 8 | // import { } from '@fortawesome/fontawesome-free-solid' 9 | import { ConnectWallet } from '../components/ConnectWallet'; 10 | import { AddressContext } from "../contexts/Address" 11 | 12 | 13 | 14 | library.add(faTwitter,faDiscord,faGithub); 15 | type Props = { 16 | children: ReactNode 17 | title?: string 18 | } 19 | 20 | const Layout = ({ 21 | children, 22 | title = 'ArchiDAO', 23 | }: Props) => ( 24 | <> 25 |
26 | 27 | 28 | {title} 29 | 30 | 31 | 32 | 33 | 34 | 38 | 42 | 43 | 44 |
45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | FAQ 55 | 56 | 57 | 58 | 59 |
60 | 61 |
62 | {children} 63 |
64 | 65 |
66 | 67 | 68 | 73 | Discord  74 | 75 |    76 | 81 | GitHub  82 | . 83 | 84 | 85 |
86 | 87 | ) 88 | 89 | export default Layout 90 | -------------------------------------------------------------------------------- /components/LightBulb.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function LightBulb(props) { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default LightBulb; 14 | -------------------------------------------------------------------------------- /components/Modal/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./modal.css"; 3 | import PropTypes from "prop-types"; 4 | 5 | export default class Modal extends React.Component { 6 | onClose = e => { 7 | this.props.onClose && this.props.onClose(e); 8 | }; 9 | render() { 10 | if (!this.props.show) { 11 | return null; 12 | } 13 | return ( 14 | 23 | ); 24 | } 25 | } 26 | Modal.propTypes = { 27 | onClose: PropTypes.func.isRequired, 28 | show: PropTypes.bool.isRequired 29 | }; 30 | -------------------------------------------------------------------------------- /components/Modal/modal.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | body { 6 | background: #eee; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | } 11 | .modal { 12 | width: 500px; 13 | background: white; 14 | border: 1px solid #ccc; 15 | transition: 1.1s ease-out; 16 | box-shadow: -2rem 2rem 2rem rgba(0, 0, 0, 0.2); 17 | filter: blur(0); 18 | transform: scale(1); 19 | opacity: 1; 20 | visibility: visible; 21 | } 22 | .modal.off { 23 | opacity: 0; 24 | visibility: hidden; 25 | filter: blur(8px); 26 | transform: scale(0.33); 27 | box-shadow: 1rem 0 0 rgba(0, 0, 0, 0.2); 28 | } 29 | @supports (offset-rotation: 0deg) { 30 | offset-rotation: 0deg; 31 | offset-path: path("M 250,100 S -300,500 -700,-200"); 32 | .modal.off { 33 | offset-distance: 100%; 34 | } 35 | } 36 | @media (prefers-reduced-motion) { 37 | .modal { 38 | offset-path: none; 39 | } 40 | } 41 | .modal h2 { 42 | border-bottom: 1px solid #ccc; 43 | padding: 1rem; 44 | margin: 0; 45 | } 46 | .modal .content { 47 | padding: 1rem; 48 | } 49 | .modal .actions { 50 | border-top: 1px solid #ccc; 51 | background: #eee; 52 | padding: 0.5rem 1rem; 53 | } 54 | .modal .actions button { 55 | border: 0; 56 | background: #78f89f; 57 | border-radius: 5px; 58 | padding: 0.5rem 1rem; 59 | font-size: 0.8rem; 60 | line-height: 1; 61 | } 62 | #centered-toggle-button { 63 | position: absolute; 64 | } 65 | -------------------------------------------------------------------------------- /components/OrbitControls.jsx: -------------------------------------------------------------------------------- 1 | // components/OrbitControls.jsx 2 | 3 | import React from "react"; 4 | import { extend, useThree } from "@react-three/fiber"; 5 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; 6 | 7 | extend({ OrbitControls }); 8 | 9 | function Controls(props) { 10 | const { camera, gl } = useThree(); 11 | return ; 12 | } 13 | 14 | export default Controls; 15 | -------------------------------------------------------------------------------- /components/PrintObject.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | type Props = { 4 | content: object 5 | } 6 | 7 | const PrintObject = ({ content }: Props) => { 8 | const formattedContent: string = JSON.stringify(content, null, 2) 9 | return
{formattedContent}
10 | } 11 | 12 | export default PrintObject 13 | -------------------------------------------------------------------------------- /components/Products.tsx: -------------------------------------------------------------------------------- 1 | import products from '../data/products' 2 | import { formatCurrencyString } from 'use-shopping-cart' 3 | import { useShoppingCart } from 'use-shopping-cart/react' 4 | import { CWload } from '../components/CWload'; 5 | // import {FontAwesomeIcon} from fortawesome; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 7 | // import { library } from '@fortawesome/fontawesome-svg-core' 8 | // import { fab } from '@fortawesome/free-brands-svg-icons' 9 | // import { faCheckSquare, faCoffee } from '@fortawesome/free-solid-svg-icons' 10 | // library.add(fab, faCheckSquare, faCoffee) 11 | // // this babel macros brings in a module called moduled and next doesnt like that (which makes sense because using a built in name is a bad idea) 12 | // import { solid, regular, brands } from '@fortawesome/fontawesome-svg-core/import.macro' 13 | import React, { useState, useEffect, useCallback } from 'react' 14 | 15 | 16 | const Products = () => { 17 | const { addItem, removeItem, incrementItem, decrementItem,cartCount } = useShoppingCart() 18 | const [minusOn, setMinus] = useState(false) 19 | const [plusOn, setPlus] = useState(true) 20 | const address = CWload('Products.tsx') 21 | // try{decrementItem(product.id)} 22 | const minusAction = (product) => { 23 | 24 | if (cartCount > 1) { 25 | decrementItem(product.id) 26 | } else { setMinus(false)} 27 | checkButtons(cartCount) 28 | 29 | } 30 | 31 | const plusAction = (product) => { 32 | console.log(product) 33 | if (cartCount < 10) { 34 | addItem(product) 35 | } else { setPlus(false) } 36 | checkButtons(cartCount) 37 | } 38 | 39 | const checkButtons = (count) => { 40 | console.log(count) 41 | if (count > 1 ) {setMinus(true)} 42 | if (count < 10 ) {setPlus(true)} 43 | } 44 | 45 | if (!address) {return ( 46 |
47 | {products.map((product) => ( 48 |
49 | {product.name} 50 |

{product.name}

51 |

52 | {formatCurrencyString({ 53 | value: product.price, 54 | currency: product.currency, 55 | })} 56 |

57 |
58 | ))} 59 |
) 60 | } else{ 61 | return ( 62 |
63 | {products.map((product) => ( 64 |
65 | {product.name} 66 |

{product.name}

67 |

68 | {formatCurrencyString({ 69 | value: product.price, 70 | currency: product.currency, 71 | })} 72 |

73 | 80 | 90 |
91 | ))} 92 |
93 | )} 94 | } 95 | 96 | export default Products 97 | -------------------------------------------------------------------------------- /components/StripeTestCards.tsx: -------------------------------------------------------------------------------- 1 | const StripeTestCards = () => { 2 | return ( 3 |
4 | Use any of the{' '} 5 | 10 | Stripe test cards 11 | {' '} 12 | for this demo, e.g.{' '} 13 |
14 | 4242424242424242 15 |
16 | . 17 |
18 | ) 19 | } 20 | 21 | export default StripeTestCards 22 | -------------------------------------------------------------------------------- /components/firebase/client.tsx: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "@firebase/app"; 3 | import { getFirestore } from "@firebase/firestore"; 4 | // TODO: Add SDKs for Firebase products that you want to use 5 | // https://firebase.google.com/docs/web/setup#available-libraries 6 | 7 | // Your web app's Firebase configuration 8 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 9 | const firebaseConfig = { 10 | apiKey: "AIzaSyDbbgvth9u0l4zE390Ko1WAYd9Fs5gsjDQ", 11 | authDomain: "archidao.firebaseapp.com", 12 | projectId: "archidao", 13 | storageBucket: "archidao.appspot.com", 14 | messagingSenderId: "1064854491749", 15 | appId: "1:1064854491749:web:cf5efaebe7555167532c8c", 16 | measurementId: "G-4JFQ60YZP3" 17 | }; 18 | 19 | // Initialize Firebase 20 | const app = initializeApp(firebaseConfig); 21 | const firestore = getFirestore(app); 22 | 23 | export {firestore} 24 | -------------------------------------------------------------------------------- /components/thirdwebsdk.js: -------------------------------------------------------------------------------- 1 | import { ThirdwebSDK } from "@thirdweb-dev/sdk"; 2 | import ethers from "ethers"; 3 | import dotenv from "dotenv"; 4 | dotenv.config(); 5 | if (!process.env.PRIVATE_KEY || process.env.PRIVATE_KEY === "") { 6 | console.log("🛑 Private key not found."); 7 | } 8 | if (!process.env.ALCHEMY_API_URL || process.env.ALCHEMY_API_URL === "") { 9 | console.log("🛑 Alchemy API URL not found."); 10 | } 11 | if (!process.env.WALLET_ADDRESS || process.env.WALLET_ADDRESS === "") { 12 | console.log("🛑 Wallet Address not found."); 13 | } 14 | const sdk = new ThirdwebSDK( 15 | new ethers.Wallet( 16 | process.env.PRIVATE_KEY, 17 | ethers.getDefaultProvider(process.env.ALCHEMY_API_URL) 18 | ) 19 | ); 20 | (async () => { 21 | try { 22 | const address = await sdk.getSigner().getAddress(); 23 | console.log("SDK initialized by address:", address); 24 | } catch (err) { 25 | console.error("Failed to get the address", err); 26 | process.exit(1); 27 | } 28 | })(); 29 | export default sdk; 30 | -------------------------------------------------------------------------------- /config/index.ts: -------------------------------------------------------------------------------- 1 | export const CURRENCY = 'usd' 2 | // Set your amount limits: Use float for decimal currencies and 3 | // Integer for zero-decimal currencies: https://stripe.com/docs/currencies#zero-decimal. 4 | export const MIN_AMOUNT = 10.0 5 | export const MAX_AMOUNT = 5000.0 6 | export const AMOUNT_STEP = 5.0 7 | -------------------------------------------------------------------------------- /contexts/Address/index.jsx: -------------------------------------------------------------------------------- 1 | import React, {useState, createContext} from "react" 2 | import { reducer, initialState } from "./reducer" 3 | 4 | export const AddressContext = createContext() 5 | 6 | export const AddressProvider = ({ children }) => { 7 | const [state, setState] = useState(initialState) 8 | 9 | return ( 10 | 11 | { children } 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /contexts/Address/reducer.js: -------------------------------------------------------------------------------- 1 | export const reducer = (state, action) => { 2 | switch (action.type) { 3 | case "toggle_button": 4 | return { 5 | ...state, 6 | active: !state.active 7 | } 8 | 9 | default: 10 | return state 11 | } 12 | } 13 | 14 | export const initialState = { 15 | active: 'init', 16 | address: 'init', 17 | } 18 | -------------------------------------------------------------------------------- /contexts/web3/index.js: -------------------------------------------------------------------------------- 1 | import {createContext,useState} from "react" 2 | 3 | export const Web3Context = createContext() 4 | 5 | export function Web3Provider({ children }){ 6 | const [addressx , setAddressx] = useState({address: '',active: false, transaction: ''}) 7 | const setAddress = (address) => {setAddressx({address:address})} 8 | return ( 9 | 10 | { children } 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /data/products.ts: -------------------------------------------------------------------------------- 1 | const product = [ 2 | { 3 | name: 'ArchiDAO NFT', 4 | description: 'ArchiDAO Wearable', 5 | id: 'archida_nft_0000', 6 | price: 9000, 7 | image: 8 | 'https://ipfs.io/ipfs/QmPYAPNFB373TrM9TJN59RDuZgSpUDW9qvqBktaSS4vDEH?filename=nft_.png', 9 | attribution: 'Photo by Archidao.io', 10 | currency: 'USD', 11 | }, 12 | ] 13 | export default product 14 | -------------------------------------------------------------------------------- /lib/firebase.js: -------------------------------------------------------------------------------- 1 | import * as admin from 'firebase-admin'; 2 | 3 | if (!admin.apps.length) { 4 | admin.initializeApp({ 5 | credential: admin.credential.cert({ 6 | projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, 7 | clientEmail: process.env.FIREBASE_CLIENT_EMAIL, 8 | privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n') 9 | }) 10 | }); 11 | } 12 | 13 | const db = admin.firestore(); 14 | 15 | export { db }; 16 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | typescript: { 3 | // !! WARN !! 4 | // Dangerously allow production builds to successfully complete even if 5 | // your project has type errors. 6 | // !! WARN !! 7 | ignoreBuildErrors: true, 8 | }, 9 | future: { 10 | webpack5: true, // by default, if you customize webpack config, they switch back to version 4. 11 | // Looks like backward compatibility approach. 12 | 13 | }, 14 | webpack(config) { 15 | config.experiments = { topLevelAwait: true,layers:true }; 16 | config.resolve.fallback = { 17 | ...config.resolve.fallback, // if you miss it, all the other options in fallback, specified 18 | // by next.js will be dropped. Doesn't make much sense, but how it is 19 | fs: false, // the solution 20 | os: false, 21 | child_process: false, 22 | module: "empty", 23 | }; 24 | 25 | return config; 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "SET PORT=8000 && next", 5 | "build": "next build", 6 | "start": "next start" 7 | }, 8 | "dependencies": { 9 | "@firebase/analytics": "^0.7.9", 10 | "@firebase/app": "^0.7.24", 11 | "@firebase/firestore": "^3.4.9", 12 | "@fortawesome/fontawesome-free": "^6.1.1", 13 | "@fortawesome/fontawesome-svg-core": "^6.1.1", 14 | "@fortawesome/free-brands-svg-icons": "^6.1.1", 15 | "@fortawesome/free-solid-svg-icons": "^6.1.1", 16 | "@fortawesome/react-fontawesome": "^0.1.18", 17 | "@react-hook/mouse-position": "^4.1.3", 18 | "@react-three/cannon": "^6.3.0", 19 | "@react-three/drei": "^9.7.1", 20 | "@react-three/fiber": "^8.0.13", 21 | "@reduxjs/toolkit": "^1.8.1", 22 | "@stripe/react-stripe-js": "1.7.0", 23 | "@stripe/stripe-js": "1.22.0", 24 | "@thirdweb-dev/react": "^2.2.6", 25 | "@thirdweb-dev/sdk": "^2.2.4", 26 | "axios": "^0.27.2", 27 | "babel-plugin-macros": "^3.1.0", 28 | "browser-env": "^3.3.0", 29 | "dashify": "^2.0.0", 30 | "dotenv": "^16.0.1", 31 | "ethers": "^5.6.5", 32 | "firebase": "9.8.1", 33 | "firebase-admin": "^10.2.0", 34 | "fontawesome": "^5.6.3", 35 | "fs": "^0.0.1-security", 36 | "fx": "^20.0.2", 37 | "micro": "^9.3.4", 38 | "micro-cors": "^0.1.1", 39 | "net": "^1.0.2", 40 | "next": "^12.1.6", 41 | "react": "^18.0.0-rc.3", 42 | "react-colorful": "^5.5.1", 43 | "react-dom": "^18.0.0-rc.3", 44 | "react-firebase-hooks": "^5.0.3", 45 | "react-firebaseui": "^6.0.0", 46 | "rooks": "^5.11.0", 47 | "state-pool": "^0.7.1", 48 | "stripe": "8.200.0", 49 | "swr": "^1.3.0", 50 | "three": "^0.140.0", 51 | "three-stdlib": "^2.10.1", 52 | "tls": "^0.0.1", 53 | "use-shopping-cart": "3.0.5", 54 | "valtio": "^1.6.0", 55 | "vercel": "^24.2.0", 56 | "web3": "^1.7.3" 57 | }, 58 | "devDependencies": { 59 | "@types/micro": "^7.3.3", 60 | "@types/micro-cors": "^0.1.0", 61 | "@types/node": "^13.1.2", 62 | "@types/react": "^16.9.17", 63 | "typescript": "4.5.5" 64 | }, 65 | "browser": { 66 | "fs": false, 67 | "path": false, 68 | "os": false, 69 | "child_process": false 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { NextPage } from 'next' 2 | import Link from 'next/link' 3 | import Layout from '../components/Layout' 4 | 5 | const IndexPage: NextPage = () => { 6 | return ( 7 | 8 |






9 |

404 | NOTHING HERE

10 |
11 | ) 12 | } 13 | 14 | export default IndexPage 15 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app' 2 | import { ThirdwebProvider } from '@thirdweb-dev/react'; 3 | import { AddressProvider } from "../contexts/Address" 4 | import { Web3Provider } from "../contexts/web3" 5 | 6 | import ReactDOM from 'react-dom' 7 | import { library } from '@fortawesome/fontawesome-svg-core' 8 | import { fab } from '@fortawesome/free-brands-svg-icons' 9 | import { faCheckSquare, faCoffee } from '@fortawesome/free-solid-svg-icons' 10 | 11 | library.add(fab, faCheckSquare, faCoffee) 12 | 13 | 14 | 15 | import '../styles.css' 16 | 17 | function MyApp({ Component, pageProps }: AppProps) { 18 | const desiredChainId = 80001; 19 | /* Make sure that your app is wrazpped with these contexts. 20 | * If you're using React, you'll have to replace the Component setup with {children} 21 | */ 22 | return ( 23 | <> 24 | 25 | 26 | 27 | {/* @ts-ignore */} 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | } 35 | 36 | export default MyApp 37 | -------------------------------------------------------------------------------- /pages/_app.xjsx: -------------------------------------------------------------------------------- 1 | import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react"; 2 | 3 | const MyApp = () => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default MyApp 12 | -------------------------------------------------------------------------------- /pages/admin/edit/[id].js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useRouter } from 'next/router' 3 | import dashify from 'dashify'; 4 | import axios from 'axios'; 5 | 6 | const EditEntry = () => { 7 | const router = useRouter() 8 | const [content, setContent] = useState({ 9 | title: undefined, 10 | body: undefined, 11 | }) 12 | 13 | useEffect(async () => { 14 | const { id } = router.query; 15 | if (id) { 16 | const res = await axios.get(`/api/entry/${id}`); 17 | const { title, body } = res.data; 18 | setContent({ 19 | title, 20 | body 21 | }) 22 | } 23 | }, [router]) 24 | 25 | const onChange = (e) => { 26 | const { value, name } = e.target; 27 | setContent(prevState => ({ ...prevState, [name]: value })); 28 | } 29 | 30 | const onSubmit = async (e) => { 31 | const { id } = router.query 32 | const { title, body } = content; 33 | console.log(id, title, body); 34 | await axios.put(`/api/entry/${id}`, { 35 | slug: dashify(title), 36 | title, 37 | body, 38 | }); 39 | } 40 | 41 | const onDelete = async () => { 42 | const { id } = router.query; 43 | await axios.delete(`/api/entry/${id}`); 44 | router.back(); 45 | } 46 | 47 | return ( 48 |
49 | 50 | 56 | 57 |