diff --git a/src/Apps/Invoice/Components/AddressForm.tsx b/src/Apps/Invoice/Components/AddressForm.tsx new file mode 100644 index 00000000000..4601313868a --- /dev/null +++ b/src/Apps/Invoice/Components/AddressForm.tsx @@ -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 ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/src/Apps/Invoice/Components/AddressFormWithCreditCard.tsx b/src/Apps/Invoice/Components/AddressFormWithCreditCard.tsx new file mode 100644 index 00000000000..7a407fc74ed --- /dev/null +++ b/src/Apps/Invoice/Components/AddressFormWithCreditCard.tsx @@ -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 ( + }> + { + 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 + /> + + + + ) +} diff --git a/src/Apps/Invoice/Components/InvoiceLineItems.tsx b/src/Apps/Invoice/Components/InvoiceLineItems.tsx new file mode 100644 index 00000000000..ece44845ded --- /dev/null +++ b/src/Apps/Invoice/Components/InvoiceLineItems.tsx @@ -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 = ({ + invoice, +}) => { + const data = useFragment(InvoiceLineItemsFragment, invoice) + + return ( + + + Items + + + }> + {data.lineItems.map(({ description, amount }, index) => ( + <> + + {description} + {amount} + + + ))} + + + ) +} + +const InvoiceLineItemsFragment = graphql` + fragment InvoiceLineItems_invoice on Invoice { + lineItems { + description + amount(precision: 2) + } + } +` diff --git a/src/Apps/Invoice/Components/InvoicePaymentForm.tsx b/src/Apps/Invoice/Components/InvoicePaymentForm.tsx new file mode 100644 index 00000000000..f0eef8e1a0d --- /dev/null +++ b/src/Apps/Invoice/Components/InvoicePaymentForm.tsx @@ -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 = 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 ( + + onSubmit={handleSubmit} + initialValues={{ address: emptyAddress, creditCard: false }} + > + {({ isSubmitting, isValid }) => { + return ( +
+ + + + + ) + }} + + ) +} diff --git a/src/Apps/Invoice/Components/InvoicePayments.tsx b/src/Apps/Invoice/Components/InvoicePayments.tsx new file mode 100644 index 00000000000..abd2b65832f --- /dev/null +++ b/src/Apps/Invoice/Components/InvoicePayments.tsx @@ -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 = ({ + invoice, +}) => { + const data = useFragment(InvoicePaymentsFragment, invoice) + + return ( + + + Payments + + + }> + {data.payments + .filter(({ successful }) => successful) + .map(({ id, createdAt, amount, creditCard }, index) => { + const creditCardInfo = creditCard + ? `${creditCard.brand} ending in ${creditCard.lastDigits}` + : null + return ( + <> + + + {createdAt} + + + {creditCardInfo && ( + + {creditCardInfo} + + )} + + + + {amount} + + + + + ) + })} + + + ) +} + +const InvoicePaymentsFragment = graphql` + fragment InvoicePayments_invoice on Invoice { + payments { + id + successful + createdAt(format: "MMM D, YYYY") + amount(precision: 2) + creditCard { + brand + lastDigits + } + } + } +` diff --git a/src/Apps/Invoice/Hooks/useCreateTokenAndSubmit.ts b/src/Apps/Invoice/Hooks/useCreateTokenAndSubmit.ts new file mode 100644 index 00000000000..87e892f50be --- /dev/null +++ b/src/Apps/Invoice/Hooks/useCreateTokenAndSubmit.ts @@ -0,0 +1,105 @@ +import { + CardCvcElement, + CardExpiryElement, + CardNumberElement, + useElements, + useStripe, +} from "@stripe/react-stripe-js" +import createLogger from "Utils/logger" +import { toStripeAddress } from "Components/Address/AddressForm" +import { + stripeCardElementNotFound, + stripeNotLoadedErrorMessage, +} from "Apps/Auction/Components/Form/Utils/errorMessages" +import { FormikHelpers } from "formik" +import { AddressFormValues } from "Apps/Invoice/Components/AddressForm" +import { useMakeInvoicePayment } from "Apps/Invoice/Hooks/useMakeInvoicePayment" +import { InvoicePaymentFormProps } from "Apps/Invoice/Components/InvoicePaymentForm" +import { useToasts } from "@artsy/palette" + +const logger = createLogger("useCreateTokenAndSubmit") + +export interface UseCreateTokenAndSubmitProps extends InvoicePaymentFormProps { + onSuccess: () => void +} + +export const useCreateTokenAndSubmit = ({ + onSuccess, + ...rest +}: UseCreateTokenAndSubmitProps) => { + const stripe = useStripe() + const elements = useElements() + const { sendToast } = useToasts() + + const { submitMutation: makeInvoicePaymentMutation } = useMakeInvoicePayment() + + const createToken = async ( + values: AddressFormValues, + helpers: FormikHelpers + ) => { + if (!stripe || !elements) { + logger.error(stripeNotLoadedErrorMessage) + helpers.setStatus("SUBMISSION_FAILED") + return + } + + const cardNumberElement = elements.getElement(CardNumberElement) + const cardExpiryElement = elements.getElement(CardExpiryElement) + const cardCvcElement = elements.getElement(CardCvcElement) + + if (!cardNumberElement || !cardExpiryElement || !cardCvcElement) { + logger.error(stripeCardElementNotFound) + helpers.setStatus("SUBMISSION_FAILED") + return + } + + helpers.setSubmitting(true) + + try { + const { error, token } = await stripe.createToken( + cardNumberElement, + toStripeAddress(values.address) + ) + + if (error) { + helpers.setFieldError("creditCard", error.message) + return + } + + await makeInvoicePaymentMutation({ + variables: { + input: { + creditCardToken: token.id, + provider: "stripe", + ...rest, + }, + }, + rejectIf: res => { + if (res.createInvoicePayment?.responseOrError?.mutationError) { + const errorMessage = + res.createInvoicePayment.responseOrError.mutationError.message + + helpers.setFieldError("creditCard", errorMessage) + + return errorMessage + } + }, + }) + + sendToast({ + variant: "success", + message: "Payment successful", + }) + + onSuccess() + } catch (error) { + helpers.setStatus("SUBMISSION_FAILED") + } finally { + helpers.setSubmitting(false) + } + } + + return { + createToken, + } +} diff --git a/src/Apps/Invoice/Hooks/useFormContext.ts b/src/Apps/Invoice/Hooks/useFormContext.ts new file mode 100644 index 00000000000..a38a23d8422 --- /dev/null +++ b/src/Apps/Invoice/Hooks/useFormContext.ts @@ -0,0 +1,7 @@ +import { AddressFormValues } from "Apps/Invoice/Components/AddressForm" +import { useFormikContext } from "formik" + +export const useFormContext = () => { + const context = useFormikContext() + return context +} diff --git a/src/Apps/Invoice/Hooks/useMakeInvoicePayment.ts b/src/Apps/Invoice/Hooks/useMakeInvoicePayment.ts new file mode 100644 index 00000000000..f1474a5f803 --- /dev/null +++ b/src/Apps/Invoice/Hooks/useMakeInvoicePayment.ts @@ -0,0 +1,25 @@ +import { useMutation } from "Utils/Hooks/useMutation" +import { graphql } from "react-relay" +import { useMakeInvoicePaymentMutation } from "__generated__/useMakeInvoicePaymentMutation.graphql" + +export const useMakeInvoicePayment = () => { + return useMutation({ + mutation: graphql` + mutation useMakeInvoicePaymentMutation( + $input: CreateInvoicePaymentInput! + ) { + createInvoicePayment(input: $input) { + __typename + + responseOrError { + ... on CreateInvoicePaymentFailure { + mutationError { + message + } + } + } + } + } + `, + }) +} diff --git a/src/Apps/Invoice/InvoiceApp.tsx b/src/Apps/Invoice/InvoiceApp.tsx new file mode 100644 index 00000000000..ba5066d814f --- /dev/null +++ b/src/Apps/Invoice/InvoiceApp.tsx @@ -0,0 +1,51 @@ +import { Box, Separator, Spacer, Text } from "@artsy/palette" +import { InvoiceApp_invoice$key } from "__generated__/InvoiceApp_invoice.graphql" +import { HttpError } from "found" +import { graphql, useFragment } from "react-relay" + +interface InvoiceAppProps { + invoice: InvoiceApp_invoice$key +} + +export const InvoiceApp: React.FC = ({ + invoice, + children, +}) => { + const data = useFragment(InvoiceAppFragment, invoice) + + if (!data) { + throw new HttpError(404) + } + + const { number, readyAt } = data + + return ( + + + + + + Invoice + + + Invoice #{number} +
+ Date: {readyAt} +
+
+ + + + + + {children} +
+ ) +} + +const InvoiceAppFragment = graphql` + fragment InvoiceApp_invoice on Invoice { + number + readyAt(format: "MMM D, YYYY") + } +` diff --git a/src/Apps/Invoice/Routes/InvoiceDetailRoute.tsx b/src/Apps/Invoice/Routes/InvoiceDetailRoute.tsx new file mode 100644 index 00000000000..2933896513a --- /dev/null +++ b/src/Apps/Invoice/Routes/InvoiceDetailRoute.tsx @@ -0,0 +1,99 @@ +import { Box, Button, Join, Separator, Text } from "@artsy/palette" +import { InvoiceDetailRoute_invoice$key } from "__generated__/InvoiceDetailRoute_invoice.graphql" +import { InvoiceLineItems } from "Apps/Invoice/Components/InvoiceLineItems" +import { InvoicePayments } from "Apps/Invoice/Components/InvoicePayments" +import { graphql, useFragment } from "react-relay" +import { RouterLink } from "System/Components/RouterLink" +import { useRouter } from "System/Hooks/useRouter" + +interface InvoiceDetailRouteProps { + invoice: InvoiceDetailRoute_invoice$key +} + +export const InvoiceDetailRoute: React.FC = ({ + invoice, +}) => { + const data = useFragment(InvoiceDetailRouteFragment, invoice) + + const { name, email, state, payments, externalNote, remaining } = data + + const hasSuccessfulPayments = + payments.filter(({ successful }) => successful).length > 0 + + const isPaid = state === "PAID" + const { match } = useRouter() + const token = match.params.token + const paymentRoute = `/invoice/${token}/payment` + + return ( + <> + }> + + + + FROM + + Artsy +
+ 401 Broadway, 25th Floor + New York, NY 10013 +
+ + + + TO + + + Name: {name} + +
+ + Email: {email} + +
+
+ + + + {hasSuccessfulPayments && ( + <> + + + )} + + + {externalNote && {externalNote}} + + {remaining} + + +
+ + {!isPaid && ( + + + + + + )} + + ) +} + +const InvoiceDetailRouteFragment = graphql` + fragment InvoiceDetailRoute_invoice on Invoice { + name + email + state + + payments { + successful + } + + externalNote + remaining(precision: 2) + + ...InvoiceLineItems_invoice + ...InvoicePayments_invoice + } +` diff --git a/src/Apps/Invoice/Routes/InvoicePaymentRoute.tsx b/src/Apps/Invoice/Routes/InvoicePaymentRoute.tsx new file mode 100644 index 00000000000..4122cd00fcb --- /dev/null +++ b/src/Apps/Invoice/Routes/InvoicePaymentRoute.tsx @@ -0,0 +1,44 @@ +import { Box, Text } from "@artsy/palette" +import { InvoicePaymentRoute_invoice$key } from "__generated__/InvoicePaymentRoute_invoice.graphql" +import { InvoicePaymentForm } from "Apps/Invoice/Components/InvoicePaymentForm" +import { CreditCardInputProvider } from "Components/CreditCardInput" +import { graphql, useFragment } from "react-relay" +import { useRouter } from "System/Hooks/useRouter" + +interface InvoicePaymentRouteProps { + invoice: InvoicePaymentRoute_invoice$key +} + +export const InvoicePaymentRoute: React.FC = ({ + invoice, +}) => { + const data = useFragment(InvoicePaymentRouteFragment, invoice) + + const { remaining, internalID, remainingMinor } = data + const { match } = useRouter() + const token = match.params.token + + return ( + + + Make a payment: {remaining} + + + + + + + ) +} + +const InvoicePaymentRouteFragment = graphql` + fragment InvoicePaymentRoute_invoice on Invoice { + remaining(precision: 2) + internalID + remainingMinor + } +` diff --git a/src/Apps/Invoice/invoiceRoutes.tsx b/src/Apps/Invoice/invoiceRoutes.tsx new file mode 100644 index 00000000000..671e969c8d7 --- /dev/null +++ b/src/Apps/Invoice/invoiceRoutes.tsx @@ -0,0 +1,79 @@ +import loadable from "@loadable/component" +import { graphql } from "react-relay" +import { RouteProps } from "System/Router/Route" + +const InvoiceApp = loadable( + () => import(/* webpackChunkName: "invoiceBundle" */ "./InvoiceApp"), + { + resolveComponent: component => component.InvoiceApp, + } +) + +const InvoiceDetailRoute = loadable( + () => + import( + /* webpackChunkName: "invoiceBundle" */ "./Routes/InvoiceDetailRoute" + ), + { + resolveComponent: component => component.InvoiceDetailRoute, + } +) + +const InvoicePaymentRoute = loadable( + () => + import( + /* webpackChunkName: "invoiceBundle" */ "./Routes/InvoicePaymentRoute" + ), + { + resolveComponent: component => component.InvoicePaymentRoute, + } +) + +export const invoiceRoutes: RouteProps[] = [ + { + path: "/invoice/:token", + getComponent: () => InvoiceApp, + onClientSideRender: () => { + InvoiceApp.preload() + }, + query: graphql` + query invoiceRoutes_InvoiceQuery($token: String!) { + invoice(token: $token) { + ...InvoiceApp_invoice + } + } + `, + children: [ + { + path: "", + getComponent: () => InvoiceDetailRoute, + onClientSideRender: () => { + InvoiceDetailRoute.preload() + }, + query: graphql` + query invoiceRoutes_InvoiceDetailQuery($token: String!) { + invoice(token: $token) { + ...InvoiceDetailRoute_invoice + } + } + `, + cacheConfig: { force: true }, + }, + + { + path: "payment", + getComponent: () => InvoicePaymentRoute, + onClientSideRender: () => { + InvoicePaymentRoute.preload() + }, + query: graphql` + query invoiceRoutes_InvoicePaymentQuery($token: String!) { + invoice(token: $token) { + ...InvoicePaymentRoute_invoice + } + } + `, + }, + ], + }, +] diff --git a/src/__generated__/InvoiceApp_invoice.graphql.ts b/src/__generated__/InvoiceApp_invoice.graphql.ts new file mode 100644 index 00000000000..c0d9f0cb28e --- /dev/null +++ b/src/__generated__/InvoiceApp_invoice.graphql.ts @@ -0,0 +1,56 @@ +/** + * @generated SignedSource<<6870df65d15bfe29ff90b78dcc51ea82>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { Fragment, ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type InvoiceApp_invoice$data = { + readonly number: string; + readonly readyAt: string | null | undefined; + readonly " $fragmentType": "InvoiceApp_invoice"; +}; +export type InvoiceApp_invoice$key = { + readonly " $data"?: InvoiceApp_invoice$data; + readonly " $fragmentSpreads": FragmentRefs<"InvoiceApp_invoice">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "InvoiceApp_invoice", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "number", + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "format", + "value": "MMM D, YYYY" + } + ], + "kind": "ScalarField", + "name": "readyAt", + "storageKey": "readyAt(format:\"MMM D, YYYY\")" + } + ], + "type": "Invoice", + "abstractKey": null +}; + +(node as any).hash = "ee1271afa542a1df6d58fa2d3a7edda5"; + +export default node; diff --git a/src/__generated__/InvoiceDetailRoute_invoice.graphql.ts b/src/__generated__/InvoiceDetailRoute_invoice.graphql.ts new file mode 100644 index 00000000000..55d0ca8708f --- /dev/null +++ b/src/__generated__/InvoiceDetailRoute_invoice.graphql.ts @@ -0,0 +1,113 @@ +/** + * @generated SignedSource<<9bbe210d85fc8192b0ba37a76cb58162>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { Fragment, ReaderFragment } from 'relay-runtime'; +export type InvoiceState = "CANCELED" | "DRAFT" | "PAID" | "READY" | "%future added value"; +import { FragmentRefs } from "relay-runtime"; +export type InvoiceDetailRoute_invoice$data = { + readonly email: string | null | undefined; + readonly externalNote: string | null | undefined; + readonly name: string | null | undefined; + readonly payments: ReadonlyArray<{ + readonly successful: boolean; + }>; + readonly remaining: string | null | undefined; + readonly state: InvoiceState; + readonly " $fragmentSpreads": FragmentRefs<"InvoiceLineItems_invoice" | "InvoicePayments_invoice">; + readonly " $fragmentType": "InvoiceDetailRoute_invoice"; +}; +export type InvoiceDetailRoute_invoice$key = { + readonly " $data"?: InvoiceDetailRoute_invoice$data; + readonly " $fragmentSpreads": FragmentRefs<"InvoiceDetailRoute_invoice">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "InvoiceDetailRoute_invoice", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "InvoicePayment", + "kind": "LinkedField", + "name": "payments", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "successful", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "externalNote", + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "precision", + "value": 2 + } + ], + "kind": "ScalarField", + "name": "remaining", + "storageKey": "remaining(precision:2)" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "InvoiceLineItems_invoice" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "InvoicePayments_invoice" + } + ], + "type": "Invoice", + "abstractKey": null +}; + +(node as any).hash = "213d0a23e701bdfdd8ef4ffb3293313d"; + +export default node; diff --git a/src/__generated__/InvoiceLineItems_invoice.graphql.ts b/src/__generated__/InvoiceLineItems_invoice.graphql.ts new file mode 100644 index 00000000000..2e210175d70 --- /dev/null +++ b/src/__generated__/InvoiceLineItems_invoice.graphql.ts @@ -0,0 +1,69 @@ +/** + * @generated SignedSource<<7fe51c611f81107e575a050701d99219>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { Fragment, ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type InvoiceLineItems_invoice$data = { + readonly lineItems: ReadonlyArray<{ + readonly amount: string | null | undefined; + readonly description: string; + }>; + readonly " $fragmentType": "InvoiceLineItems_invoice"; +}; +export type InvoiceLineItems_invoice$key = { + readonly " $data"?: InvoiceLineItems_invoice$data; + readonly " $fragmentSpreads": FragmentRefs<"InvoiceLineItems_invoice">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "InvoiceLineItems_invoice", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "InvoiceLineItem", + "kind": "LinkedField", + "name": "lineItems", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "precision", + "value": 2 + } + ], + "kind": "ScalarField", + "name": "amount", + "storageKey": "amount(precision:2)" + } + ], + "storageKey": null + } + ], + "type": "Invoice", + "abstractKey": null +}; + +(node as any).hash = "a99daa90c2adba129b513e3fdc252924"; + +export default node; diff --git a/src/__generated__/InvoicePaymentRoute_invoice.graphql.ts b/src/__generated__/InvoicePaymentRoute_invoice.graphql.ts new file mode 100644 index 00000000000..880f43af4a9 --- /dev/null +++ b/src/__generated__/InvoicePaymentRoute_invoice.graphql.ts @@ -0,0 +1,64 @@ +/** + * @generated SignedSource<<1b598c9663dfe36fbd6b62c406707e62>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { Fragment, ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type InvoicePaymentRoute_invoice$data = { + readonly internalID: string; + readonly remaining: string | null | undefined; + readonly remainingMinor: number; + readonly " $fragmentType": "InvoicePaymentRoute_invoice"; +}; +export type InvoicePaymentRoute_invoice$key = { + readonly " $data"?: InvoicePaymentRoute_invoice$data; + readonly " $fragmentSpreads": FragmentRefs<"InvoicePaymentRoute_invoice">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "InvoicePaymentRoute_invoice", + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "precision", + "value": 2 + } + ], + "kind": "ScalarField", + "name": "remaining", + "storageKey": "remaining(precision:2)" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "internalID", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "remainingMinor", + "storageKey": null + } + ], + "type": "Invoice", + "abstractKey": null +}; + +(node as any).hash = "dd89c4ffd0c09a71a3e0c18d5128d57c"; + +export default node; diff --git a/src/__generated__/InvoicePayments_invoice.graphql.ts b/src/__generated__/InvoicePayments_invoice.graphql.ts new file mode 100644 index 00000000000..700d8d3dfc2 --- /dev/null +++ b/src/__generated__/InvoicePayments_invoice.graphql.ts @@ -0,0 +1,120 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { Fragment, ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type InvoicePayments_invoice$data = { + readonly payments: ReadonlyArray<{ + readonly amount: string | null | undefined; + readonly createdAt: string | null | undefined; + readonly creditCard: { + readonly brand: string; + readonly lastDigits: string; + } | null | undefined; + readonly id: string; + readonly successful: boolean; + }>; + readonly " $fragmentType": "InvoicePayments_invoice"; +}; +export type InvoicePayments_invoice$key = { + readonly " $data"?: InvoicePayments_invoice$data; + readonly " $fragmentSpreads": FragmentRefs<"InvoicePayments_invoice">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "InvoicePayments_invoice", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "InvoicePayment", + "kind": "LinkedField", + "name": "payments", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "successful", + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "format", + "value": "MMM D, YYYY" + } + ], + "kind": "ScalarField", + "name": "createdAt", + "storageKey": "createdAt(format:\"MMM D, YYYY\")" + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "precision", + "value": 2 + } + ], + "kind": "ScalarField", + "name": "amount", + "storageKey": "amount(precision:2)" + }, + { + "alias": null, + "args": null, + "concreteType": "CreditCard", + "kind": "LinkedField", + "name": "creditCard", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "brand", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastDigits", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Invoice", + "abstractKey": null +}; + +(node as any).hash = "93f07c74b980a580fdba33d0bd598938"; + +export default node; diff --git a/src/__generated__/invoiceRoutes_InvoiceDetailQuery.graphql.ts b/src/__generated__/invoiceRoutes_InvoiceDetailQuery.graphql.ts new file mode 100644 index 00000000000..b81d79159d5 --- /dev/null +++ b/src/__generated__/invoiceRoutes_InvoiceDetailQuery.graphql.ts @@ -0,0 +1,236 @@ +/** + * @generated SignedSource<<4853612b420100dcf5341a5e02d9a2ef>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type invoiceRoutes_InvoiceDetailQuery$variables = { + token: string; +}; +export type invoiceRoutes_InvoiceDetailQuery$data = { + readonly invoice: { + readonly " $fragmentSpreads": FragmentRefs<"InvoiceDetailRoute_invoice">; + } | null | undefined; +}; +export type invoiceRoutes_InvoiceDetailQuery = { + response: invoiceRoutes_InvoiceDetailQuery$data; + variables: invoiceRoutes_InvoiceDetailQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "token" + } +], +v1 = [ + { + "kind": "Variable", + "name": "token", + "variableName": "token" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = [ + { + "kind": "Literal", + "name": "precision", + "value": 2 + } +], +v4 = { + "alias": null, + "args": (v3/*: any*/), + "kind": "ScalarField", + "name": "amount", + "storageKey": "amount(precision:2)" +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "invoiceRoutes_InvoiceDetailQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "Invoice", + "kind": "LinkedField", + "name": "invoice", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "InvoiceDetailRoute_invoice" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "invoiceRoutes_InvoiceDetailQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "Invoice", + "kind": "LinkedField", + "name": "invoice", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "InvoicePayment", + "kind": "LinkedField", + "name": "payments", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "successful", + "storageKey": null + }, + (v2/*: any*/), + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "format", + "value": "MMM D, YYYY" + } + ], + "kind": "ScalarField", + "name": "createdAt", + "storageKey": "createdAt(format:\"MMM D, YYYY\")" + }, + (v4/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "CreditCard", + "kind": "LinkedField", + "name": "creditCard", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "brand", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "lastDigits", + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "externalNote", + "storageKey": null + }, + { + "alias": null, + "args": (v3/*: any*/), + "kind": "ScalarField", + "name": "remaining", + "storageKey": "remaining(precision:2)" + }, + { + "alias": null, + "args": null, + "concreteType": "InvoiceLineItem", + "kind": "LinkedField", + "name": "lineItems", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + (v4/*: any*/), + (v2/*: any*/) + ], + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "2b9ce39a17afe7ac05b3615cf29c85b3", + "id": null, + "metadata": {}, + "name": "invoiceRoutes_InvoiceDetailQuery", + "operationKind": "query", + "text": "query invoiceRoutes_InvoiceDetailQuery(\n $token: String!\n) {\n invoice(token: $token) {\n ...InvoiceDetailRoute_invoice\n id\n }\n}\n\nfragment InvoiceDetailRoute_invoice on Invoice {\n name\n email\n state\n payments {\n successful\n id\n }\n externalNote\n remaining(precision: 2)\n ...InvoiceLineItems_invoice\n ...InvoicePayments_invoice\n}\n\nfragment InvoiceLineItems_invoice on Invoice {\n lineItems {\n description\n amount(precision: 2)\n id\n }\n}\n\nfragment InvoicePayments_invoice on Invoice {\n payments {\n id\n successful\n createdAt(format: \"MMM D, YYYY\")\n amount(precision: 2)\n creditCard {\n brand\n lastDigits\n id\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "ee553be3d159a34d9753a955455a4a23"; + +export default node; diff --git a/src/__generated__/invoiceRoutes_InvoicePaymentQuery.graphql.ts b/src/__generated__/invoiceRoutes_InvoicePaymentQuery.graphql.ts new file mode 100644 index 00000000000..b1a3f6aa8ad --- /dev/null +++ b/src/__generated__/invoiceRoutes_InvoicePaymentQuery.graphql.ts @@ -0,0 +1,134 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type invoiceRoutes_InvoicePaymentQuery$variables = { + token: string; +}; +export type invoiceRoutes_InvoicePaymentQuery$data = { + readonly invoice: { + readonly " $fragmentSpreads": FragmentRefs<"InvoicePaymentRoute_invoice">; + } | null | undefined; +}; +export type invoiceRoutes_InvoicePaymentQuery = { + response: invoiceRoutes_InvoicePaymentQuery$data; + variables: invoiceRoutes_InvoicePaymentQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "token" + } +], +v1 = [ + { + "kind": "Variable", + "name": "token", + "variableName": "token" + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "invoiceRoutes_InvoicePaymentQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "Invoice", + "kind": "LinkedField", + "name": "invoice", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "InvoicePaymentRoute_invoice" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "invoiceRoutes_InvoicePaymentQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "Invoice", + "kind": "LinkedField", + "name": "invoice", + "plural": false, + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "precision", + "value": 2 + } + ], + "kind": "ScalarField", + "name": "remaining", + "storageKey": "remaining(precision:2)" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "internalID", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "remainingMinor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "342fb5e595a8161a3ed3308adb7b6916", + "id": null, + "metadata": {}, + "name": "invoiceRoutes_InvoicePaymentQuery", + "operationKind": "query", + "text": "query invoiceRoutes_InvoicePaymentQuery(\n $token: String!\n) {\n invoice(token: $token) {\n ...InvoicePaymentRoute_invoice\n id\n }\n}\n\nfragment InvoicePaymentRoute_invoice on Invoice {\n remaining(precision: 2)\n internalID\n remainingMinor\n}\n" + } +}; +})(); + +(node as any).hash = "46da36776fb731441a3efcef9a8190ec"; + +export default node; diff --git a/src/__generated__/invoiceRoutes_InvoiceQuery.graphql.ts b/src/__generated__/invoiceRoutes_InvoiceQuery.graphql.ts new file mode 100644 index 00000000000..3855803c572 --- /dev/null +++ b/src/__generated__/invoiceRoutes_InvoiceQuery.graphql.ts @@ -0,0 +1,127 @@ +/** + * @generated SignedSource<<7a54c7deb80c7fac1b6b660d98921d1e>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type invoiceRoutes_InvoiceQuery$variables = { + token: string; +}; +export type invoiceRoutes_InvoiceQuery$data = { + readonly invoice: { + readonly " $fragmentSpreads": FragmentRefs<"InvoiceApp_invoice">; + } | null | undefined; +}; +export type invoiceRoutes_InvoiceQuery = { + response: invoiceRoutes_InvoiceQuery$data; + variables: invoiceRoutes_InvoiceQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "token" + } +], +v1 = [ + { + "kind": "Variable", + "name": "token", + "variableName": "token" + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "invoiceRoutes_InvoiceQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "Invoice", + "kind": "LinkedField", + "name": "invoice", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "InvoiceApp_invoice" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "invoiceRoutes_InvoiceQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "Invoice", + "kind": "LinkedField", + "name": "invoice", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "number", + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "format", + "value": "MMM D, YYYY" + } + ], + "kind": "ScalarField", + "name": "readyAt", + "storageKey": "readyAt(format:\"MMM D, YYYY\")" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "b34dab2604f8f34105035285a3dc36ad", + "id": null, + "metadata": {}, + "name": "invoiceRoutes_InvoiceQuery", + "operationKind": "query", + "text": "query invoiceRoutes_InvoiceQuery(\n $token: String!\n) {\n invoice(token: $token) {\n ...InvoiceApp_invoice\n id\n }\n}\n\nfragment InvoiceApp_invoice on Invoice {\n number\n readyAt(format: \"MMM D, YYYY\")\n}\n" + } +}; +})(); + +(node as any).hash = "22f7db6b7486ac49dafcd58802edaf42"; + +export default node; diff --git a/src/__generated__/useMakeInvoicePaymentMutation.graphql.ts b/src/__generated__/useMakeInvoicePaymentMutation.graphql.ts new file mode 100644 index 00000000000..452cfe17c90 --- /dev/null +++ b/src/__generated__/useMakeInvoicePaymentMutation.graphql.ts @@ -0,0 +1,166 @@ +/** + * @generated SignedSource<<41eb028e2f86698e979ade9779094e69>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Mutation } from 'relay-runtime'; +export type CreateInvoicePaymentInput = { + amountMinor: number; + clientMutationId?: string | null | undefined; + creditCardToken: string; + invoiceID: string; + invoiceToken: string; + provider: string; +}; +export type useMakeInvoicePaymentMutation$variables = { + input: CreateInvoicePaymentInput; +}; +export type useMakeInvoicePaymentMutation$data = { + readonly createInvoicePayment: { + readonly __typename: "CreateInvoicePaymentPayload"; + readonly responseOrError: { + readonly mutationError?: { + readonly message: string; + } | null | undefined; + } | null | undefined; + } | null | undefined; +}; +export type useMakeInvoicePaymentMutation = { + response: useMakeInvoicePaymentMutation$data; + variables: useMakeInvoicePaymentMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v3 = { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "GravityMutationError", + "kind": "LinkedField", + "name": "mutationError", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "message", + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "CreateInvoicePaymentFailure", + "abstractKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "useMakeInvoicePaymentMutation", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "CreateInvoicePaymentPayload", + "kind": "LinkedField", + "name": "createInvoicePayment", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "responseOrError", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "useMakeInvoicePaymentMutation", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": "CreateInvoicePaymentPayload", + "kind": "LinkedField", + "name": "createInvoicePayment", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "concreteType": null, + "kind": "LinkedField", + "name": "responseOrError", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "76aec82acae55653903cd0d5d009bc36", + "id": null, + "metadata": {}, + "name": "useMakeInvoicePaymentMutation", + "operationKind": "mutation", + "text": "mutation useMakeInvoicePaymentMutation(\n $input: CreateInvoicePaymentInput!\n) {\n createInvoicePayment(input: $input) {\n __typename\n responseOrError {\n __typename\n ... on CreateInvoicePaymentFailure {\n mutationError {\n message\n }\n }\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "eb46640c0326ab58cf0482b6b1821af5"; + +export default node; diff --git a/src/routes.tsx b/src/routes.tsx index dc02c34923b..8a3afc16672 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -25,6 +25,7 @@ import { geneRoutes } from "Apps/Gene/geneRoutes" import { homeRoutes } from "Apps/Home/homeRoutes" import { identityVerificationRoutes } from "Apps/IdentityVerification/identityVerificationRoutes" import { institutionPartnershipsRoutes } from "Apps/InstitutionPartnerships/institutionPartnershipsRoutes" +import { invoiceRoutes } from "Apps/Invoice/invoiceRoutes" import { jobsRoutes } from "Apps/Jobs/jobsRoutes" import { marketingRoutes } from "Apps/Marketing/marketingRoutes" import { myCollectionRoutes } from "Apps/MyCollection/myCollectionRoutes" @@ -93,6 +94,7 @@ const ROUTES = buildAppRoutes([ homeRoutes, identityVerificationRoutes, institutionPartnershipsRoutes, + invoiceRoutes, jobsRoutes, marketingRoutes, myCollectionRoutes,