Skip to content

Commit

Permalink
Commerce: Added support for card payments in checkout (#59)
Browse files Browse the repository at this point in the history
* added pay with card option to checkout
* added server action to process payment
* moved square payment processing to utils
* added user verification for card payments
* added google pay and apple pay, restyled payment step on checkout
* added secure payments info
* [email protected] and [email protected]; added missing dep
---------
Co-authored-by: artem ash <[email protected]>
  • Loading branch information
erikrakuscek authored Mar 16, 2024
1 parent 0da8f94 commit 4c57bc5
Show file tree
Hide file tree
Showing 27 changed files with 1,052 additions and 311 deletions.
18 changes: 10 additions & 8 deletions packages/auth/components/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,16 @@ const Login: React.FC<{
</div>
) : (
<div className='flex flex-col gap-4 text-center'>
<div className='mb-4 items-center flex'>
{returnToUrl && (
<Button size='icon' variant='ghost' className='absolute' onClick={() => router.push(returnToUrl)}>
<ArrowLeft/>
</Button>
)}
{!hideHeader && <h2 className='mx-auto'>Login</h2>}
</div>
{!hideHeader && (
<div className='mb-4 items-center flex'>
{returnToUrl && (
<Button size='icon' variant='ghost' className='absolute' onClick={() => router.push(returnToUrl)}>
<ArrowLeft/>
</Button>
)}
<h2 className='mx-auto'>Login</h2>
</div>
)}
{redirectUrl === 'checkout' && <p>You will be redirected to checkout after login.</p>}
<EmailPasswordForm onSubmit={loginWithEmailPassword} isLoading={isLoading} className='mb-4'/>
<p>- <span className='font-normal font-nav'>or</span> -</p>
Expand Down
2 changes: 1 addition & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hanzo/auth",
"version": "2.0.0",
"version": "2.0.1",
"description": "Library with Firebase authentication.",
"publishConfig": {
"registry": "https://registry.npmjs.org/",
Expand Down
83 changes: 0 additions & 83 deletions packages/commerce/components/checkout/contact-info.tsx

This file was deleted.

82 changes: 17 additions & 65 deletions packages/commerce/components/checkout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,51 @@
'use client'

import { useEffect, useState } from 'react'
import { useState } from 'react'
import { ChevronLeft } from 'lucide-react'
import { observer } from 'mobx-react-lite'

import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
import { useForm } from 'react-hook-form'

import { Button, Main, Separator, Toaster } from '@hanzo/ui/primitives'
import { cn } from '@hanzo/ui/util'
import { useAuth } from '@hanzo/auth/service'
import { LoginComponent, AuthWidget } from '@hanzo/auth/components'

import ShippingInfo from './shipping-info'
import ThankYou from './thank-you'
import PayWithCrypto from './pay-with-crypto'
import PayByBankTransfer from './pay-by-bank-transfer'
import ContactInfo from './contact-info'
import { Cart } from '..'
import { useCommerce } from '../../service/context'

const contactFormSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters.'),
email: z.string().email(),
})
import Payment from './payment'

const Checkout: React.FC<{toggleCheckout: () => void}> = observer(({toggleCheckout}) => {
const auth = useAuth()
const cmmc = useCommerce()

const [currentStep, setCurrentStep] = useState(1)
const [paymentMethod, setPaymentMethod] = useState<'crypto' | 'bank' | undefined>()
const [orderId, setOrderId] = useState<string>()

const contactForm = useForm<z.infer<typeof contactFormSchema>>({
resolver: zodResolver(contactFormSchema),
defaultValues: {
name: auth.user?.displayName ?? '',
email: auth.user?.email ?? '',
},
})

useEffect(() => {
if (auth.loggedIn) {
contactForm.setValue('name', auth.user?.displayName ?? '')
contactForm.setValue('email', auth.user?.email ?? '')
}
}, [auth.loggedIn])

const selectPaymentMethod = async (method: 'crypto' | 'bank') => {
contactForm.handleSubmit(async () => {
if (auth.user) {
if (!!orderId) {
await cmmc.updateOrder(orderId, auth.user.email, method)
} else {
const id = await cmmc.createOrder(auth.user.email, method)
setOrderId(id)
}
}
setPaymentMethod(method)
setCurrentStep(2)
})()
}


const step1 = auth.loggedIn ? (
<ContactInfo form={contactForm} selectPaymentMethod={selectPaymentMethod}/>
<Payment
orderId={orderId}
setOrderId={setOrderId}
setCurrentStep={setCurrentStep}
/>
) : (
<LoginComponent hideHeader className='max-w-[20rem] mx-auto'/>
)

const step2 = paymentMethod === 'crypto' ? (
<PayWithCrypto setCurrentStep={setCurrentStep}/>
) : (
<PayByBankTransfer setCurrentStep={setCurrentStep}/>
)

const step3 = (
<ShippingInfo orderId={orderId} paymentMethod={paymentMethod} setCurrentStep={setCurrentStep}/>
const step2 = (
<ShippingInfo orderId={orderId} setCurrentStep={setCurrentStep}/>
)

const step4 = <ThankYou/>
const step3 = <ThankYou/>

const steps = [step1, step2, step3, step4]
const steps = [step1, step2, step3]

return (
<div className="fixed top-0 left-0 !max-w-none w-full h-full min-h-screen bg-background z-[51]">
<div className="fixed top-0 left-0 !max-w-none w-full h-full min-h-screen bg-background z-50">
<Toaster/>
<Main className='flex flex-col gap-1 h-full'>
<div className='flex justify-between'>
<div className='flex justify-between items-center'>
<Button
variant='ghost'
size='icon'
className='sm:ml-2 sm:mt-2'
onClick={() => {
setCurrentStep(0)
toggleCheckout()
Expand All @@ -109,18 +61,18 @@ const Checkout: React.FC<{toggleCheckout: () => void}> = observer(({toggleChecko
<Cart hideCheckout className='fixed justify-center border-none mt-10 w-1/3 max-w-[40rem]'/>
</div>

<div className='flex flex-col gap-8 sm:gap-14 col-span-5 md:col-span-3 max-w-[30rem] w-full mx-auto overflow-y-auto h-[calc(100%-40px)] sm:h-[calc(100%-48px)] py-4'>
<div className='flex flex-col gap-8 sm:gap-14 col-span-5 md:col-span-3 max-w-[30rem] w-full mx-auto overflow-y-auto h-[calc(100%-40px)] sm:h-[calc(100%-48px)] py-4 px-1'>
<div className='flex gap-2 mx-auto items-center text-xxs sm:text-base'>
<div className={cn('w-6 h-6 rounded-full border border-foreground flex flex-col justify-center items-center', currentStep === 1 ? 'bg-foreground text-muted-4' : '')}>
<div className='relative text-foreground top-4 h-0 whitespace-nowrap'>Contact</div>
<div className='relative text-foreground top-4 h-0 whitespace-nowrap'>Payment</div>
</div>
<Separator className='w-[4rem] sm:w-[6rem]'/>
<div className={cn('w-6 h-6 rounded-full border border-foreground flex flex-col justify-center items-center', currentStep === 2 ? 'bg-foreground text-muted-4' : '')}>
<div className='relative text-foreground top-4 h-0 whitespace-nowrap'>Payment</div>
<div className='relative text-foreground top-4 h-0 whitespace-nowrap'>Delivery</div>
</div>
<Separator className='w-[4rem] sm:w-[6rem]'/>
<div className={cn('w-6 h-6 rounded-full border border-foreground flex flex-col justify-center items-center', currentStep === 3 ? 'bg-foreground text-muted-4' : '')}>
<div className='relative text-foreground top-4 h-0 whitespace-nowrap'>Delivery</div>
<div className='relative text-foreground top-4 h-0 whitespace-nowrap'>Done!</div>
</div>
</div>
{steps[currentStep - 1]}
Expand Down
76 changes: 0 additions & 76 deletions packages/commerce/components/checkout/pay-by-bank-transfer.tsx

This file was deleted.

59 changes: 59 additions & 0 deletions packages/commerce/components/checkout/payment/contact-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@hanzo/ui/primitives/form'
import { Input } from '@hanzo/ui/primitives'
import type { UseFormReturn } from 'react-hook-form'

const ContactInfo: React.FC<{
form: UseFormReturn<{
name: string
email: string
}, any, {
name: string
email: string
}>,
}> = ({
form,
}) => {
return (
<Form {...form}>
<form className='text-left'>
<div className='flex flex-col sm:flex-row gap-2'>
<FormField
control={form.control}
name='name'
render={({ field }) => (
<FormItem className='space-y-1 w-full'>
<FormLabel>Full name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='email'
render={({ field }) => (
<FormItem className='space-y-1 w-full'>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</form>
</Form>
)
}

export default ContactInfo
Loading

0 comments on commit 4c57bc5

Please sign in to comment.