Skip to content

Commit

Permalink
Merge pull request #14654 from artsy/invoice-app
Browse files Browse the repository at this point in the history
feat: add InvoiceApp to retrieve an invoice by token, and allow a payment
  • Loading branch information
mzikherman authored Oct 16, 2024
2 parents ba2ae9e + a6ef171 commit fe8b249
Show file tree
Hide file tree
Showing 22 changed files with 1,821 additions and 0 deletions.
118 changes: 118 additions & 0 deletions src/Apps/Invoice/Components/AddressForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Column, GridColumns, Input } from "@artsy/palette"
import { CountrySelect } from "Components/CountrySelect"
import { Address } from "Components/Address/AddressForm"
import { useFormContext } from "Apps/Invoice/Hooks/useFormContext"

export interface AddressFormValues {
address: Address
creditCard?: boolean
}

export const AddressForm = () => {
const { handleChange, handleBlur, errors, values, touched } = useFormContext()

return (
<GridColumns>
<Column span={12}>
<Input
name="address.name"
title="Full Name"
placeholder="Enter name"
autoComplete="name"
autoFocus
value={values.address?.name}
onChange={handleChange}
onBlur={handleBlur}
error={touched.address?.name && errors.address?.name}
required
/>
</Column>

<Column span={6}>
<CountrySelect
name="address.country"
title="Country"
// TODO: Accept a value prop in Select
// @ts-ignore
value={values.address.country}
onChange={handleChange}
onBlur={handleBlur}
error={touched.address?.country && errors.address?.country}
required
// FIXME: There's extra margin between title and select in palette
// than the title and select in input. Open PR to palette
mt={-0.5}
/>
</Column>

<Column span={6}>
<Input
name="address.postalCode"
title="Postal Code"
placeholder="Add postal code"
autoComplete="postal-code"
value={values.address?.postalCode}
onChange={handleChange}
onBlur={handleBlur}
error={touched.address?.postalCode && errors.address?.postalCode}
required
/>
</Column>

<Column span={6}>
<Input
name="address.addressLine1"
title="Address Line 1"
placeholder="Add address"
autoComplete="address-line1"
value={values.address?.addressLine1}
onChange={handleChange}
onBlur={handleBlur}
error={touched.address?.addressLine1 && errors.address?.addressLine1}
required
/>
</Column>

<Column span={6}>
<Input
name="address.addressLine2"
title="Address Line 2"
placeholder="Add address line 2"
autoComplete="address-line2"
value={values.address?.addressLine2}
onChange={handleChange}
onBlur={handleBlur}
error={touched.address?.addressLine2 && errors.address?.addressLine2}
/>
</Column>

<Column span={6}>
<Input
name="address.city"
title="City"
placeholder="Enter city"
autoComplete="address-level2"
value={values.address?.city}
onChange={handleChange}
onBlur={handleBlur}
error={touched.address?.city && errors.address?.city}
required
/>
</Column>

<Column span={6}>
<Input
name="address.region"
title="State, Province, or Region"
placeholder="Add state, province, or region"
autoComplete="address-level1"
value={values.address?.region}
onChange={handleChange}
onBlur={handleBlur}
error={touched.address?.region && errors.address?.region}
required
/>
</Column>
</GridColumns>
)
}
42 changes: 42 additions & 0 deletions src/Apps/Invoice/Components/AddressFormWithCreditCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Join, Spacer } from "@artsy/palette"
import { CreditCardInput } from "Components/CreditCardInput"
import { AddressForm } from "./AddressForm"
import { useFormContext } from "Apps/Invoice/Hooks/useFormContext"

export const AddressFormWithCreditCard: React.FC = () => {
const {
setFieldValue,
setFieldTouched,
setFieldError,
errors,
touched,
} = useFormContext()

return (
<Join separator={<Spacer y={2} />}>
<CreditCardInput
error={touched.creditCard && errors.creditCard}
onChange={event => {
setFieldTouched("creditCard", true)

if (event.error?.message) {
setFieldValue("creditCard", false)
setFieldError("creditCard", event.error?.message)
return
}
if (!event.complete) {
setFieldValue("creditCard", false)
return
}
if (event.complete) {
setFieldValue("creditCard", true)
return
}
}}
required
/>

<AddressForm />
</Join>
)
}
45 changes: 45 additions & 0 deletions src/Apps/Invoice/Components/InvoiceLineItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Box, Join, Separator, Text } from "@artsy/palette"
import { graphql, useFragment } from "react-relay"
import { InvoiceLineItems_invoice$key } from "__generated__/InvoiceLineItems_invoice.graphql"

interface InvoiceLineItemsProps {
invoice: InvoiceLineItems_invoice$key
}

export const InvoiceLineItems: React.FC<InvoiceLineItemsProps> = ({
invoice,
}) => {
const data = useFragment(InvoiceLineItemsFragment, invoice)

return (
<Box my={4}>
<Text variant="lg" fontWeight="bold">
Items
</Text>

<Join separator={<Separator my={2} />}>
{data.lineItems.map(({ description, amount }, index) => (
<>
<Box
display="flex"
justifyContent="space-between"
mt={index === 0 ? 2 : 1}
>
<Text variant="sm">{description}</Text>
<Text variant="sm">{amount}</Text>
</Box>
</>
))}
</Join>
</Box>
)
}

const InvoiceLineItemsFragment = graphql`
fragment InvoiceLineItems_invoice on Invoice {
lineItems {
description
amount(precision: 2)
}
}
`
52 changes: 52 additions & 0 deletions src/Apps/Invoice/Components/InvoicePaymentForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Button } from "@artsy/palette"
import { AddressFormValues } from "Apps/Invoice/Components/AddressForm"
import { AddressFormWithCreditCard } from "Apps/Invoice/Components/AddressFormWithCreditCard"
import { useCreateTokenAndSubmit } from "Apps/Invoice/Hooks/useCreateTokenAndSubmit"
import { emptyAddress } from "Components/Address/AddressForm"
import { Formik, Form } from "formik"
import { useRouter } from "System/Hooks/useRouter"

export interface InvoicePaymentFormProps {
invoiceID: string
invoiceToken: string
amountMinor: number
}

export const InvoicePaymentForm: React.FC<InvoicePaymentFormProps> = props => {
const { match, router } = useRouter()
const token = match.params.token
const invoiceRoute = `/invoice/${token}`
const onSuccess = () => {
router.push(invoiceRoute)
}
const { createToken: handleSubmit } = useCreateTokenAndSubmit({
onSuccess,
...props,
})

return (
<Formik<AddressFormValues>
onSubmit={handleSubmit}
initialValues={{ address: emptyAddress, creditCard: false }}
>
{({ isSubmitting, isValid }) => {
return (
<Form>
<AddressFormWithCreditCard />

<Button
mt={2}
size="large"
width="100%"
loading={isSubmitting}
disabled={!isValid}
type="submit"
>
Pay now
</Button>
</Form>
)
}}
</Formik>
)
}
67 changes: 67 additions & 0 deletions src/Apps/Invoice/Components/InvoicePayments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Box, Column, GridColumns, Join, Separator, Text } from "@artsy/palette"
import { graphql, useFragment } from "react-relay"
import { InvoicePayments_invoice$key } from "__generated__/InvoicePayments_invoice.graphql"

interface InvoiceLineItemsProps {
invoice: InvoicePayments_invoice$key
}

export const InvoicePayments: React.FC<InvoiceLineItemsProps> = ({
invoice,
}) => {
const data = useFragment(InvoicePaymentsFragment, invoice)

return (
<Box my={4}>
<Text variant="lg" fontWeight="bold">
Payments
</Text>

<Join separator={<Separator />}>
{data.payments
.filter(({ successful }) => successful)
.map(({ id, createdAt, amount, creditCard }, index) => {
const creditCardInfo = creditCard
? `${creditCard.brand} ending in ${creditCard.lastDigits}`
: null
return (
<>
<GridColumns my={2} key={id}>
<Column span={2}>
<Text variant="sm">{createdAt}</Text>
</Column>
<Column span={6}>
{creditCardInfo && (
<Text variant="sm" textAlign={"center"}>
{creditCardInfo}
</Text>
)}
</Column>
<Column span={4}>
<Text variant="sm" textAlign={"right"}>
{amount}
</Text>
</Column>
</GridColumns>
</>
)
})}
</Join>
</Box>
)
}

const InvoicePaymentsFragment = graphql`
fragment InvoicePayments_invoice on Invoice {
payments {
id
successful
createdAt(format: "MMM D, YYYY")
amount(precision: 2)
creditCard {
brand
lastDigits
}
}
}
`
Loading

0 comments on commit fe8b249

Please sign in to comment.