From fa2a7254a8cde269b152c0fb675d8c1d2c12b0aa Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 10:19:37 +0100 Subject: [PATCH 01/78] payment-process-stripe - add flow context --- src/components/donation-flow/DonationFlowContext.tsx | 11 +++++++++++ .../donation-flow/common/RadioCardGroup.tsx | 2 -- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 src/components/donation-flow/DonationFlowContext.tsx diff --git a/src/components/donation-flow/DonationFlowContext.tsx b/src/components/donation-flow/DonationFlowContext.tsx new file mode 100644 index 000000000..2d86b44f7 --- /dev/null +++ b/src/components/donation-flow/DonationFlowContext.tsx @@ -0,0 +1,11 @@ +import React, { PropsWithChildren } from 'react' + +const DonationFlowContext = React.createContext({ + stripePaymentIntentId: null, +}) + +export const DonationFlowProvider = ({ children }: PropsWithChildren) => { + const [stripePaymentIntentId, setStripePaymentIntentId] = React.useState(null) + const value = { stripePaymentIntentId, setStripePaymentIntentId } + return {children} +} diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index c99755692..f9a02fc32 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -77,8 +77,6 @@ function RadioCardGroup({ options, defaultValue }: RadioCardGroupProps) { const handleChange = (event: React.ChangeEvent) => { setValue(event.target.value) } - - console.log(theme.typography.h1) return ( Date: Thu, 5 Jan 2023 10:25:03 +0100 Subject: [PATCH 02/78] payment-process-stripe - add choose amount component --- src/components/donation-flow/ChooseAmount.tsx | 26 +++++++++++++++++++ .../donation-flow/DonationFlowPage.tsx | 2 ++ 2 files changed, 28 insertions(+) create mode 100644 src/components/donation-flow/ChooseAmount.tsx diff --git a/src/components/donation-flow/ChooseAmount.tsx b/src/components/donation-flow/ChooseAmount.tsx new file mode 100644 index 000000000..cc8a7311c --- /dev/null +++ b/src/components/donation-flow/ChooseAmount.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import RadioButtonGroup from 'components/common/form/RadioButtonGroup' +import { useSinglePriceList } from 'common/hooks/donation' +import { moneyPublic } from 'common/util/money' +import { useTranslation } from 'react-i18next' + +function ChooseAmount() { + const { data: prices } = useSinglePriceList() + const { t } = useTranslation('one-time-donation') + return ( + Number(a.unit_amount) - Number(b.unit_amount)) + .map((v) => ({ + label: moneyPublic(Number(v.unit_amount)), + value: String(Number(v.unit_amount)), + })) + .concat({ label: t('first-step.other'), value: 'other' }) || [] + } + /> + ) +} + +export default ChooseAmount diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 3bde1fe08..b5d849ea0 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -12,6 +12,7 @@ import { import Layout from 'components/layout/Layout' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' +import ChooseAmount from './ChooseAmount' // import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' // import PaymentDetailsStripeForm from 'components/donations/stripe/PaymentDetailsStripeForm' @@ -130,6 +131,7 @@ export default function DonationFlowPage({ slug }: { slug: string }) { /> )} */} {/* */} + From 4fd4bd4a1b2df9805b397d32c2b89938ebf79c62 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 10:36:02 +0100 Subject: [PATCH 03/78] payment-process-stripe - connect the accordion group to work with formik now --- .../common/RadioAccordionGroup.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 24d6d9e8c..35f2ac35b 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -12,6 +12,7 @@ import { TextField, } from '@mui/material' import { styled } from '@mui/material/styles' +import { useField } from 'formik' import theme from 'common/theme' import CardIcon from '../icons/CardIcon' import BankIcon from '../icons/BankIcon' @@ -82,27 +83,26 @@ type Option = { export interface RadioAccordionGroupProps extends RadioGroupProps { options: Option[] + name: string defaultValue?: string } -function RadioAccordionGroup({ options, defaultValue }: RadioAccordionGroupProps) { - const [value, setValue] = React.useState(defaultValue) - +function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProps) { + const [field, meta, { setValue }] = useField(name) const handleChange = (event: React.ChangeEvent) => { setValue(event.target.value) } return ( - + + }} + {...rest}> {options.map((option) => ( } label={option.label} /> } icon={option.icon} - selected={value === option.value} + selected={field.value === option.value} content={option.content} /> ))} From 95a0553fb962bb4b986fa1dd33579205602bd217 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 10:37:28 +0100 Subject: [PATCH 04/78] payment-process-stripe - connect the accordion card group to work with formik --- .../donation-flow/common/RadioAccordionGroup.tsx | 1 - .../donation-flow/common/RadioCardGroup.tsx | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 35f2ac35b..7c14b772e 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -84,7 +84,6 @@ type Option = { export interface RadioAccordionGroupProps extends RadioGroupProps { options: Option[] name: string - defaultValue?: string } function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProps) { diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index f9a02fc32..2b69a5132 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -14,6 +14,7 @@ import { styled, lighten } from '@mui/material/styles' import theme from 'common/theme' import CardIcon from '../icons/CardIcon' import BankIcon from '../icons/BankIcon' +import { useField } from 'formik' export const StyledRadioCardItem = styled(Card)(() => ({ padding: theme.spacing(2), @@ -68,21 +69,20 @@ type Option = { export interface RadioCardGroupProps extends RadioGroupProps { options: Option[] - defaultValue?: string + name: string } -function RadioCardGroup({ options, defaultValue }: RadioCardGroupProps) { - const [value, setValue] = React.useState(defaultValue) - +function RadioCardGroup({ options, name }: RadioCardGroupProps) { + const [field, meta, { setValue }] = useField(name) const handleChange = (event: React.ChangeEvent) => { setValue(event.target.value) } return ( - + {options.map((option) => ( @@ -109,7 +109,7 @@ function RadioCardGroup({ options, defaultValue }: RadioCardGroupProps) { /> } icon={option.icon} - selected={value === option.value} + selected={field.value === option.value} /> ))} From f9f134a33fe4b4b91f4d162ddc14042083225891 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 10:49:50 +0100 Subject: [PATCH 05/78] payment-process-stripe - add formik to the flow form --- .../donation-flow/DonationFlowForm.tsx | 52 +++++++++++++++++++ .../donation-flow/DonationFlowPage.tsx | 5 +- .../{ => steps}/ChooseAmount.tsx | 0 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/components/donation-flow/DonationFlowForm.tsx rename src/components/donation-flow/{ => steps}/ChooseAmount.tsx (100%) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx new file mode 100644 index 000000000..2e910f3ab --- /dev/null +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import yup from 'yup' +import { Form, Formik } from 'formik' +import { FirstStep } from 'gql/donations' +import ChooseAmount from './steps/ChooseAmount' + +const initialValues: FirstStep = { + amount: '', + payment: 'card', +} + +export const validationSchema: yup.SchemaOf = yup + .object() + .defined() + .shape({ + payment: yup.string().required().oneOf(['card', 'bank']), + amount: yup.string().when('payment', { + is: 'card', + // Here we should fetch the possible payments to put into the oneOf, but it's not that important + then: yup.string().required(), + }), + otherAmount: yup.number().when('amount', { + is: 'other', + then: yup.number().min(1, 'one-time-donation:errors-fields.other-amount').required(), + }), + }) + +export function DonationFlowForm() { + return ( + { + console.log(values) + }} + validateOnMount + validateOnBlur> + {({ isSubmitting, handleSubmit, isValid }) => ( +
+ + + )} +
+ ) +} diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index b5d849ea0..8943c1f56 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -12,7 +12,8 @@ import { import Layout from 'components/layout/Layout' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' -import ChooseAmount from './ChooseAmount' +import ChooseAmount from './steps/ChooseAmount' +import { DonationFlowForm } from './DonationFlowForm' // import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' // import PaymentDetailsStripeForm from 'components/donations/stripe/PaymentDetailsStripeForm' @@ -131,7 +132,7 @@ export default function DonationFlowPage({ slug }: { slug: string }) { /> )} */} {/* */} - + diff --git a/src/components/donation-flow/ChooseAmount.tsx b/src/components/donation-flow/steps/ChooseAmount.tsx similarity index 100% rename from src/components/donation-flow/ChooseAmount.tsx rename to src/components/donation-flow/steps/ChooseAmount.tsx From 0db5c83b8e5b0d6d6a7de15b4b1a42183ea4bc2b Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 11:24:44 +0100 Subject: [PATCH 06/78] payment-process-stripe - add the choose amount from the old donation flow --- .../donation-flow/DonationFlowContext.tsx | 24 ++- .../donation-flow/DonationFlowForm.tsx | 13 +- .../donation-flow/DonationFlowPage.tsx | 93 ++++----- .../donation-flow/steps/ChooseAmount.tsx | 177 ++++++++++++++++-- .../stripe/stripe-fee-calculator.ts | 59 ++++++ src/pages/campaigns/donation-v2/[slug].tsx | 33 ++++ 6 files changed, 327 insertions(+), 72 deletions(-) create mode 100644 src/components/donation-flow/stripe/stripe-fee-calculator.ts create mode 100644 src/pages/campaigns/donation-v2/[slug].tsx diff --git a/src/components/donation-flow/DonationFlowContext.tsx b/src/components/donation-flow/DonationFlowContext.tsx index 2d86b44f7..4a462290f 100644 --- a/src/components/donation-flow/DonationFlowContext.tsx +++ b/src/components/donation-flow/DonationFlowContext.tsx @@ -1,11 +1,25 @@ import React, { PropsWithChildren } from 'react' +import { useRouter } from 'next/router' +import { useViewCampaign } from 'common/hooks/campaigns' +import CenteredSpinner from 'components/common/CenteredSpinner' +import { CampaignResponse } from 'gql/campaigns' -const DonationFlowContext = React.createContext({ - stripePaymentIntentId: null, -}) +type DonationContext = { + stripePaymentIntentId: string + setStripePaymentIntentId: React.Dispatch> + campaign: CampaignResponse +} + +export const DonationFlowContext = React.createContext({} as DonationContext) export const DonationFlowProvider = ({ children }: PropsWithChildren) => { - const [stripePaymentIntentId, setStripePaymentIntentId] = React.useState(null) - const value = { stripePaymentIntentId, setStripePaymentIntentId } + //get the campaign with react-query and pass it to the context + const router = useRouter() + const slug = String(router.query.slug) + const { data, isLoading } = useViewCampaign(slug) + const [stripePaymentIntentId, setStripePaymentIntentId] = React.useState('') + if (isLoading || !data) return + const { campaign } = data + const value = { stripePaymentIntentId, setStripePaymentIntentId, campaign } return {children} } diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 2e910f3ab..2d20e764e 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -1,12 +1,18 @@ import React from 'react' -import yup from 'yup' +import * as yup from 'yup' import { Form, Formik } from 'formik' import { FirstStep } from 'gql/donations' import ChooseAmount from './steps/ChooseAmount' +import { CardRegion } from 'gql/donations.enums' +import SubmitButton from 'components/common/form/SubmitButton' -const initialValues: FirstStep = { +const initialValues = { amount: '', payment: 'card', + amountWithFees: 0, + cardIncludeFees: false, + cardRegion: CardRegion.EU, + otherAmount: 0, } export const validationSchema: yup.SchemaOf = yup @@ -35,7 +41,7 @@ export function DonationFlowForm() { }} validateOnMount validateOnBlur> - {({ isSubmitting, handleSubmit, isValid }) => ( + {({ handleSubmit }) => (
+ Submit )} diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 8943c1f56..a5eac5801 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -14,6 +14,7 @@ import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' import ChooseAmount from './steps/ChooseAmount' import { DonationFlowForm } from './DonationFlowForm' +import { DonationFlowProvider } from './DonationFlowContext' // import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' // import PaymentDetailsStripeForm from 'components/donations/stripe/PaymentDetailsStripeForm' @@ -80,50 +81,51 @@ export default function DonationFlowPage({ slug }: { slug: string }) { const beneficiaryAvatarSource = beneficiaryCampaignPictureUrl(campaign) return ( - - - - {/* A11Y TODO: Translate alt text */} - Campaign banner image - + + - - + m="0 auto" + marginTop={theme.spacing(matches ? 20 : 25)}> + + {/* A11Y TODO: Translate alt text */} + Campaign banner image + + + + - - - - {campaign.title} - - - {/* {paymentIntentMutation.isLoading ? ( + + + + {campaign.title} + + + {/* {paymentIntentMutation.isLoading ? ( ) : ( )} */} - {/* */} - + {/* */} + + - - + + ) } diff --git a/src/components/donation-flow/steps/ChooseAmount.tsx b/src/components/donation-flow/steps/ChooseAmount.tsx index cc8a7311c..9ff8d801c 100644 --- a/src/components/donation-flow/steps/ChooseAmount.tsx +++ b/src/components/donation-flow/steps/ChooseAmount.tsx @@ -1,26 +1,165 @@ -import React from 'react' -import RadioButtonGroup from 'components/common/form/RadioButtonGroup' +import React, { useEffect } from 'react' +import { Trans, useTranslation } from 'next-i18next' +import { useMediaQuery, Box, Collapse, Grid, InputAdornment, Typography } from '@mui/material' +import { styled } from '@mui/material/styles' +import { useField, useFormikContext } from 'formik' + +import { OneTimeDonation } from 'gql/donations' +import { CardRegion } from 'gql/donations.enums' + +import theme from 'common/theme' import { useSinglePriceList } from 'common/hooks/donation' -import { moneyPublic } from 'common/util/money' -import { useTranslation } from 'react-i18next' +import { moneyPublic, moneyPublicDecimals2, toMoney } from 'common/util/money' + +import RadioButtonGroup from 'components/common/form/RadioButtonGroup' +import FormTextField from 'components/common/form/FormTextField' +import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/stripe-fee-calculator' +import CheckboxField from 'components/common/form/CheckboxField' +import FormSelectField from 'components/common/form/FormSelectField' + +const PREFIX = 'FirstStep' + +const classes = { + divider: `${PREFIX}-divider`, +} -function ChooseAmount() { +// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed. +const Root = styled('div')(() => ({ + [`& .${classes.divider}`]: { + border: '1px solid #000000', + }, +})) + +export default function FirstStep() { const { data: prices } = useSinglePriceList() const { t } = useTranslation('one-time-donation') + const mobile = useMediaQuery('(max-width:600px)') + + const [amount] = useField('amount') + const [amountWithFees] = useField('amountWithFees') + const [amountWithoutFees] = useField('amountWithoutFees') + + const formik = useFormikContext() + + useEffect(() => { + const chosenAmount = + amount.value === 'other' ? toMoney(formik.values.otherAmount) : Number(formik.values.amount) + + if (formik.values.cardIncludeFees) { + formik.setFieldValue('amountWithoutFees', chosenAmount) + formik.setFieldValue( + 'amountWithFees', + stripeIncludeFeeCalculator(chosenAmount, formik.values.cardRegion), + ) + } else { + formik.setFieldValue( + 'amountWithoutFees', + chosenAmount - stripeFeeCalculator(chosenAmount, formik.values.cardRegion), + ) + formik.setFieldValue('amountWithFees', chosenAmount) + } + }, [ + formik.values.otherAmount, + formik.values.amount, + formik.values.cardIncludeFees, + formik.values.cardRegion, + ]) + return ( - Number(a.unit_amount) - Number(b.unit_amount)) - .map((v) => ({ - label: moneyPublic(Number(v.unit_amount)), - value: String(Number(v.unit_amount)), - })) - .concat({ label: t('first-step.other'), value: 'other' }) || [] - } - /> + + + {t('first-step.amount')} + + + Number(a.unit_amount) - Number(b.unit_amount)) + .map((v) => ({ + label: moneyPublic(Number(v.unit_amount)), + value: String(Number(v.unit_amount)), + })) + .concat({ label: t('first-step.other'), value: 'other' }) || [] + } + /> + + + + {t('first-step.BGN')} + + ), + }} + /> + + + {amount.value ? ( + + + + {t('third-step.card-include-fees')} + } + /> + + + + + + + + ) : null} + + ) } - -export default ChooseAmount diff --git a/src/components/donation-flow/stripe/stripe-fee-calculator.ts b/src/components/donation-flow/stripe/stripe-fee-calculator.ts new file mode 100644 index 000000000..ed71ff2f2 --- /dev/null +++ b/src/components/donation-flow/stripe/stripe-fee-calculator.ts @@ -0,0 +1,59 @@ +import { CardRegion } from 'gql/donations.enums' + +/** + * Calculates total charge amount in stotinki so that donation of netAmount is received after deducting Stripe fees + * References: + * - applied fees https://stripe.com/en-bg/pricing + * - formula for including fees: https://support.stripe.com/questions/passing-the-stripe-fee-on-to-customers + * - testing with International cards: https://stripe.com/docs/testing#international-cards + * @param netAmount expected in stotinki + * @returns + */ +export function stripeIncludeFeeCalculator(netAmount: number, region: CardRegion) { + switch (region) { + case CardRegion.EU: { + return stripeIncludeFeeCalculatorEU(netAmount) + } + case CardRegion.UK: { + return stripeIncludeFeeCalculatorUK(netAmount) + } + case CardRegion.Other: { + return stripeIncludeFeeCalculatorOther(netAmount) + } + } +} + +export function stripeIncludeFeeCalculatorEU(netAmount: number) { + return (netAmount + 50) / (1 - 0.012) +} + +export function stripeIncludeFeeCalculatorUK(netAmount: number) { + return (netAmount + 50) / (1 - 0.025) +} + +export function stripeIncludeFeeCalculatorOther(netAmount: number) { + return (netAmount + 50) / (1 - 0.029) +} + +/** + * Calculates Stripe fees based on card region + * References: + * - applied fees https://stripe.com/en-bg/pricing + * - formula for including fees: https://support.stripe.com/questions/passing-the-stripe-fee-on-to-customers + * - testing with International cards: https://stripe.com/docs/testing#international-cards + * @param chargedAmount expected in stotinki + * @returns + */ +export function stripeFeeCalculator(chargedAmount: number, region: CardRegion) { + switch (region) { + case CardRegion.EU: { + return chargedAmount * 0.012 + 50 + } + case CardRegion.UK: { + return chargedAmount * 0.025 + 50 + } + case CardRegion.Other: { + return chargedAmount * 0.029 + 50 + } + } +} diff --git a/src/pages/campaigns/donation-v2/[slug].tsx b/src/pages/campaigns/donation-v2/[slug].tsx new file mode 100644 index 000000000..bc046cc0e --- /dev/null +++ b/src/pages/campaigns/donation-v2/[slug].tsx @@ -0,0 +1,33 @@ +import { QueryClient, dehydrate } from '@tanstack/react-query' +import { GetServerSideProps, GetServerSidePropsContext } from 'next' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' + +import { endpoints } from 'service/apiEndpoints' +import { queryFnFactory } from 'service/restRequests' +import { CampaignResponse } from 'gql/campaigns' + +import DonationFlowPage from 'components/donation-flow/DonationFlowPage' + +export const getServerSideProps: GetServerSideProps = async (ctx: GetServerSidePropsContext) => { + const { slug } = ctx.query + const client = new QueryClient() + await client.prefetchQuery( + [endpoints.campaign.viewCampaign(slug as string)], + queryFnFactory(), + ) + return { + props: { + slug, + dehydratedState: dehydrate(client), + ...(await serverSideTranslations(ctx.locale ?? 'bg', [ + 'common', + 'auth', + 'validation', + 'campaigns', + 'one-time-donation', + ])), + }, + } +} + +export default DonationFlowPage From 51d70c1097a3ec49ec031819b5aefafe37981f12 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 12:54:23 +0100 Subject: [PATCH 07/78] payment-process-stripe - add disabled prop to the options on the cards --- .../donation-flow/DonationFlowForm.tsx | 6 +- .../donation-flow/DonationFlowPage.tsx | 2 +- .../donation-flow/common/RadioCardGroup.tsx | 72 +++++++++---------- .../steps/{ChooseAmount.tsx => Amount.tsx} | 4 +- .../donation-flow/steps/PaymentMethod.tsx | 43 +++++++++++ 5 files changed, 84 insertions(+), 43 deletions(-) rename src/components/donation-flow/steps/{ChooseAmount.tsx => Amount.tsx} (98%) create mode 100644 src/components/donation-flow/steps/PaymentMethod.tsx diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 2d20e764e..e3daf5b9c 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -2,9 +2,10 @@ import React from 'react' import * as yup from 'yup' import { Form, Formik } from 'formik' import { FirstStep } from 'gql/donations' -import ChooseAmount from './steps/ChooseAmount' import { CardRegion } from 'gql/donations.enums' import SubmitButton from 'components/common/form/SubmitButton' +import Amount from './steps/Amount' +import PaymentMethod from './steps/PaymentMethod' const initialValues = { amount: '', @@ -50,7 +51,8 @@ export function DonationFlowForm() { marginRight: 'auto', }} autoComplete="off"> - + + Submit )} diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index a5eac5801..0d0d03065 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -12,7 +12,7 @@ import { import Layout from 'components/layout/Layout' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' -import ChooseAmount from './steps/ChooseAmount' +import ChooseAmount from './steps/Amount' import { DonationFlowForm } from './DonationFlowForm' import { DonationFlowProvider } from './DonationFlowContext' // import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index 2b69a5132..f4506e8c7 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -12,43 +12,40 @@ import { } from '@mui/material' import { styled, lighten } from '@mui/material/styles' import theme from 'common/theme' -import CardIcon from '../icons/CardIcon' -import BankIcon from '../icons/BankIcon' import { useField } from 'formik' -export const StyledRadioCardItem = styled(Card)(() => ({ +export const BaseRadioCardItem = styled(Card)(() => ({ padding: theme.spacing(2), margin: 0, cursor: 'pointer', border: `1px solid ${theme.borders.dark}`, + width: '100%', +})) + +export const DisabledRadioCardItem = styled(BaseRadioCardItem)(() => ({ + opacity: 0.7, + backgroundColor: `${theme.palette.grey[300]} !important`, + pointerEvents: 'none', +})) + +export const SelectedRadioCardItem = styled(BaseRadioCardItem)(() => ({ + backgroundColor: lighten(theme.palette.primary.light, 0.7), })) interface StyledRadioCardItemProps extends CardProps { control: React.ReactNode icon: React.ReactNode + disabled?: boolean selected?: boolean } -// Temporarily here for testing until the components starts being used -export const testRadioOptions: Option[] = [ - { - value: 'card', - label: 'Card', - icon: , - }, - { - value: 'bank', - label: 'Bank', - icon: , - }, - { - value: 'paypal', - label: 'PayPal', - icon: , - }, -] - -function RadioCardItem({ control, icon, selected, ...rest }: StyledRadioCardItemProps) { +function RadioCardItem({ control, icon, selected, disabled, ...rest }: StyledRadioCardItemProps) { + let StyledRadioCardItem = BaseRadioCardItem + if (disabled) { + StyledRadioCardItem = DisabledRadioCardItem + } else if (selected) { + StyledRadioCardItem = SelectedRadioCardItem + } return ( ) => { setValue(event.target.value) } return ( - - + + {options.map((option) => ( - + setValue(option.value)} control={ } label={option.label} /> } icon={option.icon} - selected={field.value === option.value} + selected={field.value === option.value && !option.disabled} + disabled={option.disabled} /> ))} diff --git a/src/components/donation-flow/steps/ChooseAmount.tsx b/src/components/donation-flow/steps/Amount.tsx similarity index 98% rename from src/components/donation-flow/steps/ChooseAmount.tsx rename to src/components/donation-flow/steps/Amount.tsx index 9ff8d801c..3f9c8dabd 100644 --- a/src/components/donation-flow/steps/ChooseAmount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -17,7 +17,7 @@ import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/strip import CheckboxField from 'components/common/form/CheckboxField' import FormSelectField from 'components/common/form/FormSelectField' -const PREFIX = 'FirstStep' +const PREFIX = 'AMOUNT' const classes = { divider: `${PREFIX}-divider`, @@ -30,7 +30,7 @@ const Root = styled('div')(() => ({ }, })) -export default function FirstStep() { +export default function Amount() { const { data: prices } = useSinglePriceList() const { t } = useTranslation('one-time-donation') const mobile = useMediaQuery('(max-width:600px)') diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx new file mode 100644 index 000000000..01ab40fd3 --- /dev/null +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import { styled } from '@mui/material/styles' +import { useFormikContext } from 'formik' + +import { OneTimeDonation } from 'gql/donations' +import RadioCardGroup from '../common/RadioCardGroup' +import CardIcon from '../icons/CardIcon' +import BankIcon from '../icons/BankIcon' + +const PREFIX = 'AMOUNT' + +const classes = { + divider: `${PREFIX}-divider`, +} + +// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed. +const Root = styled('div')(() => ({ + [`& .${classes.divider}`]: { + border: '1px solid #000000', + }, +})) + +export default function PaymentMethod() { + const formik = useFormikContext() + const options = [ + { + value: 'card', + label: 'Card', + icon: , + disabled: !formik.values.amount, + }, + { + value: 'bank', + label: 'Bank Transfer', + icon: , + }, + ] + return ( + + + + ) +} From 4fc4ed93ae743fdf036e540792c0cd283daff5dc Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 16:23:46 +0100 Subject: [PATCH 08/78] payment-process-stripe - add the stripe payment intent to the context --- .env.local.example | 2 +- .../donation-flow/DonationFlowContext.tsx | 14 ++++++++++---- .../donation-flow/DonationFlowPage.tsx | 7 ------- src/components/donation-flow/steps/Amount.tsx | 19 ++++++++++++++++++- .../donation-flow/steps/PaymentMethod.tsx | 15 ++++++++++++++- .../stripe/PaymentDetailsStripeForm.tsx | 4 ---- 6 files changed, 43 insertions(+), 18 deletions(-) diff --git a/.env.local.example b/.env.local.example index 8ec1a117f..bd64f394c 100644 --- a/.env.local.example +++ b/.env.local.example @@ -32,7 +32,7 @@ GOOGLE_SECRET= ## Stripe ## ############## -STRIPE_PUBLIC_KEY= +STRIPE_PUBLIC_KEY=pk_test_51HmiW8JLlnbRmnT5ZYwSnviwwLOYo1Y4JdEVmkpzZ6HRQ8f3850Zkn9yLBowIeKytxLu9S24KS2oUU1U5OKPonkB00aGnDOyvK ## Paypal ## ############## diff --git a/src/components/donation-flow/DonationFlowContext.tsx b/src/components/donation-flow/DonationFlowContext.tsx index 4a462290f..bcc5d9bc5 100644 --- a/src/components/donation-flow/DonationFlowContext.tsx +++ b/src/components/donation-flow/DonationFlowContext.tsx @@ -3,10 +3,11 @@ import { useRouter } from 'next/router' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' import { CampaignResponse } from 'gql/campaigns' +import Stripe from 'stripe' type DonationContext = { - stripePaymentIntentId: string - setStripePaymentIntentId: React.Dispatch> + stripePaymentIntent: Stripe.PaymentIntent | null + setStripePaymentIntent: React.Dispatch> campaign: CampaignResponse } @@ -17,9 +18,14 @@ export const DonationFlowProvider = ({ children }: PropsWithChildren) => { const router = useRouter() const slug = String(router.query.slug) const { data, isLoading } = useViewCampaign(slug) - const [stripePaymentIntentId, setStripePaymentIntentId] = React.useState('') + const [stripePaymentIntent, setStripePaymentIntent] = + React.useState(null) if (isLoading || !data) return const { campaign } = data - const value = { stripePaymentIntentId, setStripePaymentIntentId, campaign } + const value = { + stripePaymentIntent, + setStripePaymentIntent, + campaign, + } return {children} } diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 0d0d03065..eb5192f2f 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -67,13 +67,6 @@ const StyledLayout = styled(Layout)(({ theme }) => ({ export default function DonationFlowPage({ slug }: { slug: string }) { const { data, isLoading } = useViewCampaign(slug) const matches = useMediaQuery('sm') - // const paymentIntentMutation = useCreatePaymentIntent({ - // amount: 100, - // currency: 'BGN', - // }) - // useEffect(() => { - // paymentIntentMutation.mutate() - // }, []) if (isLoading || !data) return const { campaign } = data diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 3f9c8dabd..050bca862 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React, { useContext, useEffect } from 'react' import { Trans, useTranslation } from 'next-i18next' import { useMediaQuery, Box, Collapse, Grid, InputAdornment, Typography } from '@mui/material' import { styled } from '@mui/material/styles' @@ -16,6 +16,8 @@ import FormTextField from 'components/common/form/FormTextField' import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/stripe-fee-calculator' import CheckboxField from 'components/common/form/CheckboxField' import FormSelectField from 'components/common/form/FormSelectField' +import { useCreatePaymentIntent } from 'service/donation' +import { DonationFlowContext } from '../DonationFlowContext' const PREFIX = 'AMOUNT' @@ -32,13 +34,28 @@ const Root = styled('div')(() => ({ export default function Amount() { const { data: prices } = useSinglePriceList() + const DonationContext = useContext(DonationFlowContext) const { t } = useTranslation('one-time-donation') const mobile = useMediaQuery('(max-width:600px)') const [amount] = useField('amount') const [amountWithFees] = useField('amountWithFees') const [amountWithoutFees] = useField('amountWithoutFees') + const paymentIntentMutation = useCreatePaymentIntent({ + amount: Number(amount.value), + currency: 'BGN', + }) + useEffect(() => { + if (amount.value) { + paymentIntentMutation.mutate() + } + }, [amount.value]) + + useEffect(() => { + const intent = paymentIntentMutation.data?.data + DonationContext.setStripePaymentIntent(intent || null) + }, [paymentIntentMutation]) const formik = useFormikContext() useEffect(() => { diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx index 01ab40fd3..88b048e15 100644 --- a/src/components/donation-flow/steps/PaymentMethod.tsx +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useContext } from 'react' import { styled } from '@mui/material/styles' import { useFormikContext } from 'formik' @@ -6,6 +6,8 @@ import { OneTimeDonation } from 'gql/donations' import RadioCardGroup from '../common/RadioCardGroup' import CardIcon from '../icons/CardIcon' import BankIcon from '../icons/BankIcon' +import PaymentDetailsStripeForm from '../stripe/PaymentDetailsStripeForm' +import { DonationFlowContext } from '../DonationFlowContext' const PREFIX = 'AMOUNT' @@ -22,6 +24,10 @@ const Root = styled('div')(() => ({ export default function PaymentMethod() { const formik = useFormikContext() + const DonationContext = useContext(DonationFlowContext) + if (!DonationContext.stripePaymentIntent) { + return null + } const options = [ { value: 'card', @@ -38,6 +44,13 @@ export default function PaymentMethod() { return ( + {DonationContext.stripePaymentIntent ? ( + + ) : ( + 'There is a problem with picking your price. Please try again later.' + )} ) } diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index ce9310288..93e746e46 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -47,10 +47,6 @@ export default function PaymentDetailsStripeForm({ clientSecret, containerProps, }: PaymentDetailsStripeFormProps) { - // const mutation = useCreatePaymentIntent({ amount: 100, currency: Currencies.BGN }) - // useEffect(() => { - // mutation.mutate() - // }, []) return ( Date: Thu, 5 Jan 2023 19:46:22 +0100 Subject: [PATCH 09/78] payment-process-stripe - add the stripe key to the runtime config --- next.config.js | 1 + src/components/donation-flow/steps/PaymentMethod.tsx | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/next.config.js b/next.config.js index b5ff66a60..9872f0a3f 100644 --- a/next.config.js +++ b/next.config.js @@ -28,6 +28,7 @@ const moduleExports = { APP_URL: process.env.APP_URL, GTM_ID: process.env.GTM_ID ?? 'GTM-TWQBXM6', PAYPAL_CLIENT_ID: process.env.PAYPAL_CLIENT_ID, + STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY, FEATURE_ENABLED: { CAMPAIGN: process.env.FEATURE_CAMPAIGN ?? false, }, diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx index 88b048e15..f37aa1270 100644 --- a/src/components/donation-flow/steps/PaymentMethod.tsx +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -25,9 +25,6 @@ const Root = styled('div')(() => ({ export default function PaymentMethod() { const formik = useFormikContext() const DonationContext = useContext(DonationFlowContext) - if (!DonationContext.stripePaymentIntent) { - return null - } const options = [ { value: 'card', From a1ba8d85237bba545a974c2df2a3164f5511e3e2 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 19:47:33 +0100 Subject: [PATCH 10/78] payment-process-stripe - add locale to the stripe payment process --- .../donation-flow/stripe/PaymentDetailsStripeForm.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index 93e746e46..2f77d4367 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -4,6 +4,7 @@ import { Elements, PaymentElement } from '@stripe/react-stripe-js' import theme from 'common/theme' import { Box, BoxProps } from '@mui/material' +import { useTranslation } from 'react-i18next' const { publicRuntimeConfig: { STRIPE_PUBLIC_KEY }, } = getConfig() @@ -47,12 +48,14 @@ export default function PaymentDetailsStripeForm({ clientSecret, containerProps, }: PaymentDetailsStripeFormProps) { + const { i18n } = useTranslation() return ( From 26c148dac9604eb7e7f50ef89f53835b488896d5 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 12 Jan 2023 20:17:41 +0200 Subject: [PATCH 11/78] dependencies - bump up minor next version to 13.1 --- next.config.js | 14 +++-- package.json | 2 +- yarn.lock | 136 ++++++++++++++++++++++++------------------------- 3 files changed, 75 insertions(+), 77 deletions(-) diff --git a/next.config.js b/next.config.js index 9872f0a3f..f5701d182 100644 --- a/next.config.js +++ b/next.config.js @@ -59,14 +59,12 @@ const moduleExports = { }, ] }, - experimental: { - modularizeImports: { - '@mui/material': { - transform: '@mui/material/{{member}}', - }, - '@mui/icons-material/?(((\\w*)?/?)*)': { - transform: '@mui/icons-material/{{ matches.[1] }}/{{member}}', - }, + modularizeImports: { + '@mui/material': { + transform: '@mui/material/{{member}}', + }, + '@mui/icons-material': { + transform: '@mui/icons-material/{{member}}', }, }, } diff --git a/package.json b/package.json index 5d07672ea..5601d8a3a 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "lodash": "^4.17.21", "mobx": "6.3.2", "mobx-react": "7.2.0", - "next": "^13.0.5", + "next": "^13.1.1", "next-auth": "^4.16.4", "next-i18next": "^13.0.0", "nookies": "^2.5.2", diff --git a/yarn.lock b/yarn.lock index 010118031..cd447a4bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1737,10 +1737,10 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:13.0.5": - version: 13.0.5 - resolution: "@next/env@npm:13.0.5" - checksum: cd005b171d602df1b01415df00e1fee79bfd7c6840f1836bedc7b0f9cb466b73e2e52a6cdcff77535538ef55eec200f5e7accb7670fa3273c625f0e0c3a79afb +"@next/env@npm:13.1.1": + version: 13.1.1 + resolution: "@next/env@npm:13.1.1" + checksum: e265b9b9e7c28023b33ba99b95e84abdfa501507e2f1b7d7b6469ab93d21edbb59fe4378f0557d12864508b747ff1fbc2f9c7de3d704ba51a37c7295fddb0ec1 languageName: node linkType: hard @@ -1760,93 +1760,93 @@ __metadata: languageName: node linkType: hard -"@next/swc-android-arm-eabi@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-android-arm-eabi@npm:13.0.5" +"@next/swc-android-arm-eabi@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-android-arm-eabi@npm:13.1.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@next/swc-android-arm64@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-android-arm64@npm:13.0.5" +"@next/swc-android-arm64@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-android-arm64@npm:13.1.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-darwin-arm64@npm:13.0.5" +"@next/swc-darwin-arm64@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-darwin-arm64@npm:13.1.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-darwin-x64@npm:13.0.5" +"@next/swc-darwin-x64@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-darwin-x64@npm:13.1.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-freebsd-x64@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-freebsd-x64@npm:13.0.5" +"@next/swc-freebsd-x64@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-freebsd-x64@npm:13.1.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm-gnueabihf@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-arm-gnueabihf@npm:13.0.5" +"@next/swc-linux-arm-gnueabihf@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-arm-gnueabihf@npm:13.1.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-arm64-gnu@npm:13.0.5" +"@next/swc-linux-arm64-gnu@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-arm64-gnu@npm:13.1.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-arm64-musl@npm:13.0.5" +"@next/swc-linux-arm64-musl@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-arm64-musl@npm:13.1.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-x64-gnu@npm:13.0.5" +"@next/swc-linux-x64-gnu@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-x64-gnu@npm:13.1.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-x64-musl@npm:13.0.5" +"@next/swc-linux-x64-musl@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-x64-musl@npm:13.1.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-win32-arm64-msvc@npm:13.0.5" +"@next/swc-win32-arm64-msvc@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-win32-arm64-msvc@npm:13.1.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-win32-ia32-msvc@npm:13.0.5" +"@next/swc-win32-ia32-msvc@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-win32-ia32-msvc@npm:13.1.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-win32-x64-msvc@npm:13.0.5" +"@next/swc-win32-x64-msvc@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-win32-x64-msvc@npm:13.1.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -8090,28 +8090,28 @@ __metadata: languageName: node linkType: hard -"next@npm:^13.0.5": - version: 13.0.5 - resolution: "next@npm:13.0.5" - dependencies: - "@next/env": 13.0.5 - "@next/swc-android-arm-eabi": 13.0.5 - "@next/swc-android-arm64": 13.0.5 - "@next/swc-darwin-arm64": 13.0.5 - "@next/swc-darwin-x64": 13.0.5 - "@next/swc-freebsd-x64": 13.0.5 - "@next/swc-linux-arm-gnueabihf": 13.0.5 - "@next/swc-linux-arm64-gnu": 13.0.5 - "@next/swc-linux-arm64-musl": 13.0.5 - "@next/swc-linux-x64-gnu": 13.0.5 - "@next/swc-linux-x64-musl": 13.0.5 - "@next/swc-win32-arm64-msvc": 13.0.5 - "@next/swc-win32-ia32-msvc": 13.0.5 - "@next/swc-win32-x64-msvc": 13.0.5 +"next@npm:^13.1.1": + version: 13.1.1 + resolution: "next@npm:13.1.1" + dependencies: + "@next/env": 13.1.1 + "@next/swc-android-arm-eabi": 13.1.1 + "@next/swc-android-arm64": 13.1.1 + "@next/swc-darwin-arm64": 13.1.1 + "@next/swc-darwin-x64": 13.1.1 + "@next/swc-freebsd-x64": 13.1.1 + "@next/swc-linux-arm-gnueabihf": 13.1.1 + "@next/swc-linux-arm64-gnu": 13.1.1 + "@next/swc-linux-arm64-musl": 13.1.1 + "@next/swc-linux-x64-gnu": 13.1.1 + "@next/swc-linux-x64-musl": 13.1.1 + "@next/swc-win32-arm64-msvc": 13.1.1 + "@next/swc-win32-ia32-msvc": 13.1.1 + "@next/swc-win32-x64-msvc": 13.1.1 "@swc/helpers": 0.4.14 caniuse-lite: ^1.0.30001406 postcss: 8.4.14 - styled-jsx: 5.1.0 + styled-jsx: 5.1.1 peerDependencies: fibers: ">= 3.1.0" node-sass: ^6.0.0 || ^7.0.0 @@ -8154,7 +8154,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: e13d8c51153b975d40002a222260352c8be048792014dd322f9a44bb9050a8f0b34c9e5c62329b3439febd87aacb6da5e0377dafff82570e79ba4f9d878f3ec5 + checksum: 97a9154d620770388e6a0a84982b094172b851f49b408ac53d83fc4110470dbef63225b9c0008e223c60759b5aa606a547bd909b15d6a166526e168877dfc802 languageName: node linkType: hard @@ -8857,7 +8857,7 @@ __metadata: lodash: ^4.17.21 mobx: 6.3.2 mobx-react: 7.2.0 - next: ^13.0.5 + next: ^13.1.1 next-auth: ^4.16.4 next-i18next: ^13.0.0 next-sitemap: ^2.5.20 @@ -10503,9 +10503,9 @@ __metadata: languageName: node linkType: hard -"styled-jsx@npm:5.1.0": - version: 5.1.0 - resolution: "styled-jsx@npm:5.1.0" +"styled-jsx@npm:5.1.1": + version: 5.1.1 + resolution: "styled-jsx@npm:5.1.1" dependencies: client-only: 0.0.1 peerDependencies: @@ -10515,7 +10515,7 @@ __metadata: optional: true babel-plugin-macros: optional: true - checksum: e5b70476fd9059147dfe35dd912e537e422a7f900cc88f80456c97da495c655598875d64de2199641d627770a7e55ed08be0fd82646bc386791fdb1d0e5af2b1 + checksum: 523a33b38603492547e861b98e29c873939b04e15fbe5ef16132c6f1e15958126647983c7d4675325038b428a5e91183d996e90141b18bdd1bbadf6e2c45b2fa languageName: node linkType: hard From ba84f0911624f792bc1723cedea0960a0c93aef2 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 12 Jan 2023 20:41:00 +0200 Subject: [PATCH 12/78] payment-process-stripe - add step splitter component --- .../donation-flow/DonationFlowForm.tsx | 4 ++ .../donation-flow/common/StepSplitter.tsx | 37 +++++++++++++++++++ src/components/donation-flow/steps/Amount.tsx | 8 ++++ 3 files changed, 49 insertions(+) create mode 100644 src/components/donation-flow/common/StepSplitter.tsx diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index e3daf5b9c..e7aa5c2e6 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -6,6 +6,7 @@ import { CardRegion } from 'gql/donations.enums' import SubmitButton from 'components/common/form/SubmitButton' import Amount from './steps/Amount' import PaymentMethod from './steps/PaymentMethod' +import StepSplitter from './common/StepSplitter' const initialValues = { amount: '', @@ -52,7 +53,10 @@ export function DonationFlowForm() { }} autoComplete="off"> + + + Submit )} diff --git a/src/components/donation-flow/common/StepSplitter.tsx b/src/components/donation-flow/common/StepSplitter.tsx new file mode 100644 index 000000000..75c356e12 --- /dev/null +++ b/src/components/donation-flow/common/StepSplitter.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import { Avatar, Box, Typography, Unstable_Grid2 as Grid2 } from '@mui/material' +import { styled } from '@mui/styles' +import theme from 'common/theme' +import { grey } from '@mui/material/colors' + +type StepSplitterProps = { + content: string + active?: boolean +} + +const StyledLine = styled('div')(() => ({ + width: '100%', + height: '1px', + backgroundColor: grey[400], + margin: '0 0 0 0', +})) + +function StepSplitter({ content, active }: StepSplitterProps) { + return ( + + + + {content} + + + + ) +} + +export default StepSplitter diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 050bca862..514be6620 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -174,6 +174,14 @@ export default function Amount() { totalAmount: moneyPublicDecimals2(amountWithFees.value), }} /> + + {t('third-step.recurring-donation')} + } + /> + ) : null} From 7f4791b87f69958e98429eab7c750cbc020c9a8a Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 02:30:19 +0100 Subject: [PATCH 13/78] payment-process-stripe - add mobile view for the payment method --- README.md | 8 ++-- .../donation-flow/DonationFlowForm.tsx | 4 +- .../donation-flow/common/StepSplitter.tsx | 5 +-- .../donation-flow/steps/PaymentMethod.tsx | 43 ++++++++++++++++--- .../stripe/PaymentDetailsStripeForm.tsx | 2 +- 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 44c21b36b..980cbc495 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ ## Perequisites - Node.js - - Installation + - Installation - [Windows / MacOS](https://nodejs.org/en/download/) - [Debian and Ubuntu based Linux distributions](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) ```shell @@ -46,10 +46,10 @@ - Installation https://yarnpkg.com/getting-started/install ```shell corepack enable - yarn set version berry + yarn set version berry ``` - make sure `cmdtest` is not installed, it has a different `yarn` command - + ## Initial setup ```shell @@ -98,7 +98,9 @@ Watch releases of this repository to be notified about future updates: ## Contributors ✨ + [![All Contributors](https://img.shields.io/badge/all_contributors-64-orange.svg?style=flat-square)](#contributors-) + Please check [contributors guide](https://github.com/podkrepi-bg/frontend/blob/master/CONTRIBUTING.md) for: diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index e7aa5c2e6..469e942bb 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -52,10 +52,10 @@ export function DonationFlowForm() { marginRight: 'auto', }} autoComplete="off"> - - + + Submit diff --git a/src/components/donation-flow/common/StepSplitter.tsx b/src/components/donation-flow/common/StepSplitter.tsx index 75c356e12..067bb4051 100644 --- a/src/components/donation-flow/common/StepSplitter.tsx +++ b/src/components/donation-flow/common/StepSplitter.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Avatar, Box, Typography, Unstable_Grid2 as Grid2 } from '@mui/material' +import { Avatar, Box, Typography } from '@mui/material' import { styled } from '@mui/styles' import theme from 'common/theme' import { grey } from '@mui/material/colors' @@ -13,12 +13,11 @@ const StyledLine = styled('div')(() => ({ width: '100%', height: '1px', backgroundColor: grey[400], - margin: '0 0 0 0', })) function StepSplitter({ content, active }: StepSplitterProps) { return ( - + ({ export default function PaymentMethod() { const formik = useFormikContext() const DonationContext = useContext(DonationFlowContext) + const { small } = useMobile() const options = [ { value: 'card', @@ -38,15 +41,43 @@ export default function PaymentMethod() { icon: , }, ] + const mobileOptions = [ + { + value: 'card', + label: 'Card', + icon: , + disabled: !formik.values.amount, + content: ( + <> + {DonationContext.stripePaymentIntent ? ( + + ) : null} + + ), + }, + { + value: 'bank', + label: 'Bank Transfer', + icon: , + content: <>TODO: Add Bank Transfer Content, + }, + ] + return ( - - {DonationContext.stripePaymentIntent ? ( - + {small ? ( + ) : ( - 'There is a problem with picking your price. Please try again later.' + <> + + {DonationContext.stripePaymentIntent ? ( + + ) : null} + )} ) diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index 2f77d4367..c8d8b2ee7 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -18,7 +18,7 @@ const appearance: Appearance = { colorDanger: theme.palette.error.main, fontFamily: "Montserrat, 'Helvetica Neue', Helvetica, Arial, sans-serif", fontSizeSm: theme.typography.pxToRem(12), - fontSizeBase: theme.typography.pxToRem(16), + fontSizeBase: theme.typography.pxToRem(14), fontSizeLg: theme.typography.pxToRem(18), fontSizeXl: theme.typography.pxToRem(20), spacingUnit: theme.spacing(0), From 196e81a2b0842ce987a976a628a7a9a8e8d80c6c Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 02:38:55 +0100 Subject: [PATCH 14/78] payment-process-stripe - add disabled and fix styling for the RadioAccordionGroup --- .../common/RadioAccordionGroup.tsx | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 7c14b772e..dec96d795 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -2,65 +2,58 @@ import React from 'react' import { Box, BoxProps, - Button, Collapse, FormControl, FormControlLabel, Radio, RadioGroup, RadioGroupProps, - TextField, } from '@mui/material' import { styled } from '@mui/material/styles' import { useField } from 'formik' import theme from 'common/theme' -import CardIcon from '../icons/CardIcon' -import BankIcon from '../icons/BankIcon' -export const StyledRadioAccordionItem = styled(Box)(() => ({ +export const BaseRadioAccordionItem = styled(Box)(() => ({ '&:not(:last-child)': { borderBottom: `1px solid ${theme.borders.dark}`, + borderTopLeftRadius: theme.borders.semiRound, + borderTopRightRadius: theme.borders.semiRound, + }, + '&:last-child': { + borderBottomLeftRadius: theme.borders.semiRound, + borderBottomRightRadius: theme.borders.semiRound, }, padding: theme.spacing(2), margin: 0, cursor: 'pointer', })) +export const DisabledRadioAccordionItem = styled(BaseRadioAccordionItem)(() => ({ + opacity: 0.7, + backgroundColor: `${theme.palette.grey[300]} !important`, + pointerEvents: 'none', +})) + interface RadioAccordionItemProps extends BoxProps { control: React.ReactNode icon: React.ReactNode content?: React.ReactNode selected?: boolean + disabled?: boolean } -// Temporarily here for testing until the components starts being used -export const testRadioOptions: Option[] = [ - { - value: 'card', - label: 'Card', - content: ( -
- - -
- ), - icon: , - }, - { - value: 'bank', - label: 'Bank', - content:
TODO: Add bank form
, - icon: , - }, -] - function RadioAccordionItem({ control, icon, selected, content, + disabled, ...rest }: RadioAccordionItemProps) { + let StyledRadioAccordionItem = BaseRadioAccordionItem + if (disabled) { + StyledRadioAccordionItem = DisabledRadioAccordionItem + } return ( @@ -79,6 +72,7 @@ type Option = { label: string content: React.ReactNode icon: React.ReactNode + disabled?: boolean } export interface RadioAccordionGroupProps extends RadioGroupProps { @@ -93,7 +87,11 @@ function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProp } return ( - + setValue(option.value)} control={ - } label={option.label} /> + } + label={option.label} + disabled={option.disabled} + /> } icon={option.icon} selected={field.value === option.value} content={option.content} + disabled={option.disabled} /> ))} From 94e15ef38050fe0842bb321e03e178e877062f54 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 10:19:37 +0100 Subject: [PATCH 15/78] 1284 - add flow context --- src/components/donation-flow/DonationFlowContext.tsx | 11 +++++++++++ .../donation-flow/common/RadioCardGroup.tsx | 2 -- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 src/components/donation-flow/DonationFlowContext.tsx diff --git a/src/components/donation-flow/DonationFlowContext.tsx b/src/components/donation-flow/DonationFlowContext.tsx new file mode 100644 index 000000000..2d86b44f7 --- /dev/null +++ b/src/components/donation-flow/DonationFlowContext.tsx @@ -0,0 +1,11 @@ +import React, { PropsWithChildren } from 'react' + +const DonationFlowContext = React.createContext({ + stripePaymentIntentId: null, +}) + +export const DonationFlowProvider = ({ children }: PropsWithChildren) => { + const [stripePaymentIntentId, setStripePaymentIntentId] = React.useState(null) + const value = { stripePaymentIntentId, setStripePaymentIntentId } + return {children} +} diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index c99755692..f9a02fc32 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -77,8 +77,6 @@ function RadioCardGroup({ options, defaultValue }: RadioCardGroupProps) { const handleChange = (event: React.ChangeEvent) => { setValue(event.target.value) } - - console.log(theme.typography.h1) return ( Date: Thu, 5 Jan 2023 10:25:03 +0100 Subject: [PATCH 16/78] 1284 - add choose amount component --- src/components/donation-flow/ChooseAmount.tsx | 26 +++++++++++++++++++ .../donation-flow/DonationFlowPage.tsx | 2 ++ 2 files changed, 28 insertions(+) create mode 100644 src/components/donation-flow/ChooseAmount.tsx diff --git a/src/components/donation-flow/ChooseAmount.tsx b/src/components/donation-flow/ChooseAmount.tsx new file mode 100644 index 000000000..cc8a7311c --- /dev/null +++ b/src/components/donation-flow/ChooseAmount.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import RadioButtonGroup from 'components/common/form/RadioButtonGroup' +import { useSinglePriceList } from 'common/hooks/donation' +import { moneyPublic } from 'common/util/money' +import { useTranslation } from 'react-i18next' + +function ChooseAmount() { + const { data: prices } = useSinglePriceList() + const { t } = useTranslation('one-time-donation') + return ( + Number(a.unit_amount) - Number(b.unit_amount)) + .map((v) => ({ + label: moneyPublic(Number(v.unit_amount)), + value: String(Number(v.unit_amount)), + })) + .concat({ label: t('first-step.other'), value: 'other' }) || [] + } + /> + ) +} + +export default ChooseAmount diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 3bde1fe08..b5d849ea0 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -12,6 +12,7 @@ import { import Layout from 'components/layout/Layout' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' +import ChooseAmount from './ChooseAmount' // import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' // import PaymentDetailsStripeForm from 'components/donations/stripe/PaymentDetailsStripeForm' @@ -130,6 +131,7 @@ export default function DonationFlowPage({ slug }: { slug: string }) { /> )} */} {/* */} + From ca0f0ac45eaef76354e229d98e20ec237a0c1501 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 10:36:02 +0100 Subject: [PATCH 17/78] 1284 - connect the accordion group to work with formik now --- .../common/RadioAccordionGroup.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 24d6d9e8c..35f2ac35b 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -12,6 +12,7 @@ import { TextField, } from '@mui/material' import { styled } from '@mui/material/styles' +import { useField } from 'formik' import theme from 'common/theme' import CardIcon from '../icons/CardIcon' import BankIcon from '../icons/BankIcon' @@ -82,27 +83,26 @@ type Option = { export interface RadioAccordionGroupProps extends RadioGroupProps { options: Option[] + name: string defaultValue?: string } -function RadioAccordionGroup({ options, defaultValue }: RadioAccordionGroupProps) { - const [value, setValue] = React.useState(defaultValue) - +function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProps) { + const [field, meta, { setValue }] = useField(name) const handleChange = (event: React.ChangeEvent) => { setValue(event.target.value) } return ( - + + }} + {...rest}> {options.map((option) => ( } label={option.label} /> } icon={option.icon} - selected={value === option.value} + selected={field.value === option.value} content={option.content} /> ))} From 4ac83a34d472c29cf7eb95c7e424ff5ed12f47eb Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 10:37:28 +0100 Subject: [PATCH 18/78] 1284 - connect the accordion card group to work with formik --- .../donation-flow/common/RadioAccordionGroup.tsx | 1 - .../donation-flow/common/RadioCardGroup.tsx | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 35f2ac35b..7c14b772e 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -84,7 +84,6 @@ type Option = { export interface RadioAccordionGroupProps extends RadioGroupProps { options: Option[] name: string - defaultValue?: string } function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProps) { diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index f9a02fc32..2b69a5132 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -14,6 +14,7 @@ import { styled, lighten } from '@mui/material/styles' import theme from 'common/theme' import CardIcon from '../icons/CardIcon' import BankIcon from '../icons/BankIcon' +import { useField } from 'formik' export const StyledRadioCardItem = styled(Card)(() => ({ padding: theme.spacing(2), @@ -68,21 +69,20 @@ type Option = { export interface RadioCardGroupProps extends RadioGroupProps { options: Option[] - defaultValue?: string + name: string } -function RadioCardGroup({ options, defaultValue }: RadioCardGroupProps) { - const [value, setValue] = React.useState(defaultValue) - +function RadioCardGroup({ options, name }: RadioCardGroupProps) { + const [field, meta, { setValue }] = useField(name) const handleChange = (event: React.ChangeEvent) => { setValue(event.target.value) } return ( - + {options.map((option) => ( @@ -109,7 +109,7 @@ function RadioCardGroup({ options, defaultValue }: RadioCardGroupProps) { /> } icon={option.icon} - selected={value === option.value} + selected={field.value === option.value} /> ))} From 75afc47b92a73dd7343a2469378781023938a13b Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 10:49:50 +0100 Subject: [PATCH 19/78] 1284 - add formik to the flow form --- .../donation-flow/DonationFlowForm.tsx | 52 +++++++++++++++++++ .../donation-flow/DonationFlowPage.tsx | 5 +- .../{ => steps}/ChooseAmount.tsx | 0 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/components/donation-flow/DonationFlowForm.tsx rename src/components/donation-flow/{ => steps}/ChooseAmount.tsx (100%) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx new file mode 100644 index 000000000..2e910f3ab --- /dev/null +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import yup from 'yup' +import { Form, Formik } from 'formik' +import { FirstStep } from 'gql/donations' +import ChooseAmount from './steps/ChooseAmount' + +const initialValues: FirstStep = { + amount: '', + payment: 'card', +} + +export const validationSchema: yup.SchemaOf = yup + .object() + .defined() + .shape({ + payment: yup.string().required().oneOf(['card', 'bank']), + amount: yup.string().when('payment', { + is: 'card', + // Here we should fetch the possible payments to put into the oneOf, but it's not that important + then: yup.string().required(), + }), + otherAmount: yup.number().when('amount', { + is: 'other', + then: yup.number().min(1, 'one-time-donation:errors-fields.other-amount').required(), + }), + }) + +export function DonationFlowForm() { + return ( + { + console.log(values) + }} + validateOnMount + validateOnBlur> + {({ isSubmitting, handleSubmit, isValid }) => ( +
+ + + )} +
+ ) +} diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index b5d849ea0..8943c1f56 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -12,7 +12,8 @@ import { import Layout from 'components/layout/Layout' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' -import ChooseAmount from './ChooseAmount' +import ChooseAmount from './steps/ChooseAmount' +import { DonationFlowForm } from './DonationFlowForm' // import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' // import PaymentDetailsStripeForm from 'components/donations/stripe/PaymentDetailsStripeForm' @@ -131,7 +132,7 @@ export default function DonationFlowPage({ slug }: { slug: string }) { /> )} */} {/* */} - + diff --git a/src/components/donation-flow/ChooseAmount.tsx b/src/components/donation-flow/steps/ChooseAmount.tsx similarity index 100% rename from src/components/donation-flow/ChooseAmount.tsx rename to src/components/donation-flow/steps/ChooseAmount.tsx From 079d6c42f775331c2c91d8e6ccbf4e7999fd684f Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 11:24:44 +0100 Subject: [PATCH 20/78] 1284 - add the choose amount from the old donation flow --- .../donation-flow/DonationFlowContext.tsx | 24 ++- .../donation-flow/DonationFlowForm.tsx | 13 +- .../donation-flow/DonationFlowPage.tsx | 93 ++++----- .../donation-flow/steps/ChooseAmount.tsx | 177 ++++++++++++++++-- .../stripe/stripe-fee-calculator.ts | 59 ++++++ src/pages/campaigns/donation-v2/[slug].tsx | 33 ++++ 6 files changed, 327 insertions(+), 72 deletions(-) create mode 100644 src/components/donation-flow/stripe/stripe-fee-calculator.ts create mode 100644 src/pages/campaigns/donation-v2/[slug].tsx diff --git a/src/components/donation-flow/DonationFlowContext.tsx b/src/components/donation-flow/DonationFlowContext.tsx index 2d86b44f7..4a462290f 100644 --- a/src/components/donation-flow/DonationFlowContext.tsx +++ b/src/components/donation-flow/DonationFlowContext.tsx @@ -1,11 +1,25 @@ import React, { PropsWithChildren } from 'react' +import { useRouter } from 'next/router' +import { useViewCampaign } from 'common/hooks/campaigns' +import CenteredSpinner from 'components/common/CenteredSpinner' +import { CampaignResponse } from 'gql/campaigns' -const DonationFlowContext = React.createContext({ - stripePaymentIntentId: null, -}) +type DonationContext = { + stripePaymentIntentId: string + setStripePaymentIntentId: React.Dispatch> + campaign: CampaignResponse +} + +export const DonationFlowContext = React.createContext({} as DonationContext) export const DonationFlowProvider = ({ children }: PropsWithChildren) => { - const [stripePaymentIntentId, setStripePaymentIntentId] = React.useState(null) - const value = { stripePaymentIntentId, setStripePaymentIntentId } + //get the campaign with react-query and pass it to the context + const router = useRouter() + const slug = String(router.query.slug) + const { data, isLoading } = useViewCampaign(slug) + const [stripePaymentIntentId, setStripePaymentIntentId] = React.useState('') + if (isLoading || !data) return + const { campaign } = data + const value = { stripePaymentIntentId, setStripePaymentIntentId, campaign } return {children} } diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 2e910f3ab..2d20e764e 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -1,12 +1,18 @@ import React from 'react' -import yup from 'yup' +import * as yup from 'yup' import { Form, Formik } from 'formik' import { FirstStep } from 'gql/donations' import ChooseAmount from './steps/ChooseAmount' +import { CardRegion } from 'gql/donations.enums' +import SubmitButton from 'components/common/form/SubmitButton' -const initialValues: FirstStep = { +const initialValues = { amount: '', payment: 'card', + amountWithFees: 0, + cardIncludeFees: false, + cardRegion: CardRegion.EU, + otherAmount: 0, } export const validationSchema: yup.SchemaOf = yup @@ -35,7 +41,7 @@ export function DonationFlowForm() { }} validateOnMount validateOnBlur> - {({ isSubmitting, handleSubmit, isValid }) => ( + {({ handleSubmit }) => (
+ Submit )} diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 8943c1f56..a5eac5801 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -14,6 +14,7 @@ import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' import ChooseAmount from './steps/ChooseAmount' import { DonationFlowForm } from './DonationFlowForm' +import { DonationFlowProvider } from './DonationFlowContext' // import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' // import PaymentDetailsStripeForm from 'components/donations/stripe/PaymentDetailsStripeForm' @@ -80,50 +81,51 @@ export default function DonationFlowPage({ slug }: { slug: string }) { const beneficiaryAvatarSource = beneficiaryCampaignPictureUrl(campaign) return ( - - - - {/* A11Y TODO: Translate alt text */} - Campaign banner image - + + - - + m="0 auto" + marginTop={theme.spacing(matches ? 20 : 25)}> + + {/* A11Y TODO: Translate alt text */} + Campaign banner image + + + + - - - - {campaign.title} - - - {/* {paymentIntentMutation.isLoading ? ( + + + + {campaign.title} + + + {/* {paymentIntentMutation.isLoading ? ( ) : ( )} */} - {/* */} - + {/* */} + + - - + + ) } diff --git a/src/components/donation-flow/steps/ChooseAmount.tsx b/src/components/donation-flow/steps/ChooseAmount.tsx index cc8a7311c..9ff8d801c 100644 --- a/src/components/donation-flow/steps/ChooseAmount.tsx +++ b/src/components/donation-flow/steps/ChooseAmount.tsx @@ -1,26 +1,165 @@ -import React from 'react' -import RadioButtonGroup from 'components/common/form/RadioButtonGroup' +import React, { useEffect } from 'react' +import { Trans, useTranslation } from 'next-i18next' +import { useMediaQuery, Box, Collapse, Grid, InputAdornment, Typography } from '@mui/material' +import { styled } from '@mui/material/styles' +import { useField, useFormikContext } from 'formik' + +import { OneTimeDonation } from 'gql/donations' +import { CardRegion } from 'gql/donations.enums' + +import theme from 'common/theme' import { useSinglePriceList } from 'common/hooks/donation' -import { moneyPublic } from 'common/util/money' -import { useTranslation } from 'react-i18next' +import { moneyPublic, moneyPublicDecimals2, toMoney } from 'common/util/money' + +import RadioButtonGroup from 'components/common/form/RadioButtonGroup' +import FormTextField from 'components/common/form/FormTextField' +import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/stripe-fee-calculator' +import CheckboxField from 'components/common/form/CheckboxField' +import FormSelectField from 'components/common/form/FormSelectField' + +const PREFIX = 'FirstStep' + +const classes = { + divider: `${PREFIX}-divider`, +} -function ChooseAmount() { +// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed. +const Root = styled('div')(() => ({ + [`& .${classes.divider}`]: { + border: '1px solid #000000', + }, +})) + +export default function FirstStep() { const { data: prices } = useSinglePriceList() const { t } = useTranslation('one-time-donation') + const mobile = useMediaQuery('(max-width:600px)') + + const [amount] = useField('amount') + const [amountWithFees] = useField('amountWithFees') + const [amountWithoutFees] = useField('amountWithoutFees') + + const formik = useFormikContext() + + useEffect(() => { + const chosenAmount = + amount.value === 'other' ? toMoney(formik.values.otherAmount) : Number(formik.values.amount) + + if (formik.values.cardIncludeFees) { + formik.setFieldValue('amountWithoutFees', chosenAmount) + formik.setFieldValue( + 'amountWithFees', + stripeIncludeFeeCalculator(chosenAmount, formik.values.cardRegion), + ) + } else { + formik.setFieldValue( + 'amountWithoutFees', + chosenAmount - stripeFeeCalculator(chosenAmount, formik.values.cardRegion), + ) + formik.setFieldValue('amountWithFees', chosenAmount) + } + }, [ + formik.values.otherAmount, + formik.values.amount, + formik.values.cardIncludeFees, + formik.values.cardRegion, + ]) + return ( - Number(a.unit_amount) - Number(b.unit_amount)) - .map((v) => ({ - label: moneyPublic(Number(v.unit_amount)), - value: String(Number(v.unit_amount)), - })) - .concat({ label: t('first-step.other'), value: 'other' }) || [] - } - /> + + + {t('first-step.amount')} + + + Number(a.unit_amount) - Number(b.unit_amount)) + .map((v) => ({ + label: moneyPublic(Number(v.unit_amount)), + value: String(Number(v.unit_amount)), + })) + .concat({ label: t('first-step.other'), value: 'other' }) || [] + } + /> + + + + {t('first-step.BGN')} + + ), + }} + /> + + + {amount.value ? ( + + + + {t('third-step.card-include-fees')} + } + /> + + + + + + + + ) : null} + + ) } - -export default ChooseAmount diff --git a/src/components/donation-flow/stripe/stripe-fee-calculator.ts b/src/components/donation-flow/stripe/stripe-fee-calculator.ts new file mode 100644 index 000000000..ed71ff2f2 --- /dev/null +++ b/src/components/donation-flow/stripe/stripe-fee-calculator.ts @@ -0,0 +1,59 @@ +import { CardRegion } from 'gql/donations.enums' + +/** + * Calculates total charge amount in stotinki so that donation of netAmount is received after deducting Stripe fees + * References: + * - applied fees https://stripe.com/en-bg/pricing + * - formula for including fees: https://support.stripe.com/questions/passing-the-stripe-fee-on-to-customers + * - testing with International cards: https://stripe.com/docs/testing#international-cards + * @param netAmount expected in stotinki + * @returns + */ +export function stripeIncludeFeeCalculator(netAmount: number, region: CardRegion) { + switch (region) { + case CardRegion.EU: { + return stripeIncludeFeeCalculatorEU(netAmount) + } + case CardRegion.UK: { + return stripeIncludeFeeCalculatorUK(netAmount) + } + case CardRegion.Other: { + return stripeIncludeFeeCalculatorOther(netAmount) + } + } +} + +export function stripeIncludeFeeCalculatorEU(netAmount: number) { + return (netAmount + 50) / (1 - 0.012) +} + +export function stripeIncludeFeeCalculatorUK(netAmount: number) { + return (netAmount + 50) / (1 - 0.025) +} + +export function stripeIncludeFeeCalculatorOther(netAmount: number) { + return (netAmount + 50) / (1 - 0.029) +} + +/** + * Calculates Stripe fees based on card region + * References: + * - applied fees https://stripe.com/en-bg/pricing + * - formula for including fees: https://support.stripe.com/questions/passing-the-stripe-fee-on-to-customers + * - testing with International cards: https://stripe.com/docs/testing#international-cards + * @param chargedAmount expected in stotinki + * @returns + */ +export function stripeFeeCalculator(chargedAmount: number, region: CardRegion) { + switch (region) { + case CardRegion.EU: { + return chargedAmount * 0.012 + 50 + } + case CardRegion.UK: { + return chargedAmount * 0.025 + 50 + } + case CardRegion.Other: { + return chargedAmount * 0.029 + 50 + } + } +} diff --git a/src/pages/campaigns/donation-v2/[slug].tsx b/src/pages/campaigns/donation-v2/[slug].tsx new file mode 100644 index 000000000..bc046cc0e --- /dev/null +++ b/src/pages/campaigns/donation-v2/[slug].tsx @@ -0,0 +1,33 @@ +import { QueryClient, dehydrate } from '@tanstack/react-query' +import { GetServerSideProps, GetServerSidePropsContext } from 'next' +import { serverSideTranslations } from 'next-i18next/serverSideTranslations' + +import { endpoints } from 'service/apiEndpoints' +import { queryFnFactory } from 'service/restRequests' +import { CampaignResponse } from 'gql/campaigns' + +import DonationFlowPage from 'components/donation-flow/DonationFlowPage' + +export const getServerSideProps: GetServerSideProps = async (ctx: GetServerSidePropsContext) => { + const { slug } = ctx.query + const client = new QueryClient() + await client.prefetchQuery( + [endpoints.campaign.viewCampaign(slug as string)], + queryFnFactory(), + ) + return { + props: { + slug, + dehydratedState: dehydrate(client), + ...(await serverSideTranslations(ctx.locale ?? 'bg', [ + 'common', + 'auth', + 'validation', + 'campaigns', + 'one-time-donation', + ])), + }, + } +} + +export default DonationFlowPage From 5cf9ea6b23bd5c2e1bee9a94d3e69b3a54d69369 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 12:54:23 +0100 Subject: [PATCH 21/78] 1284 - add disabled prop to the options on the cards --- .../donation-flow/DonationFlowForm.tsx | 6 +- .../donation-flow/DonationFlowPage.tsx | 2 +- .../donation-flow/common/RadioCardGroup.tsx | 72 +++++++++---------- .../steps/{ChooseAmount.tsx => Amount.tsx} | 4 +- .../donation-flow/steps/PaymentMethod.tsx | 43 +++++++++++ 5 files changed, 84 insertions(+), 43 deletions(-) rename src/components/donation-flow/steps/{ChooseAmount.tsx => Amount.tsx} (98%) create mode 100644 src/components/donation-flow/steps/PaymentMethod.tsx diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 2d20e764e..e3daf5b9c 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -2,9 +2,10 @@ import React from 'react' import * as yup from 'yup' import { Form, Formik } from 'formik' import { FirstStep } from 'gql/donations' -import ChooseAmount from './steps/ChooseAmount' import { CardRegion } from 'gql/donations.enums' import SubmitButton from 'components/common/form/SubmitButton' +import Amount from './steps/Amount' +import PaymentMethod from './steps/PaymentMethod' const initialValues = { amount: '', @@ -50,7 +51,8 @@ export function DonationFlowForm() { marginRight: 'auto', }} autoComplete="off"> - + + Submit )} diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index a5eac5801..0d0d03065 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -12,7 +12,7 @@ import { import Layout from 'components/layout/Layout' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' -import ChooseAmount from './steps/ChooseAmount' +import ChooseAmount from './steps/Amount' import { DonationFlowForm } from './DonationFlowForm' import { DonationFlowProvider } from './DonationFlowContext' // import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index 2b69a5132..f4506e8c7 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -12,43 +12,40 @@ import { } from '@mui/material' import { styled, lighten } from '@mui/material/styles' import theme from 'common/theme' -import CardIcon from '../icons/CardIcon' -import BankIcon from '../icons/BankIcon' import { useField } from 'formik' -export const StyledRadioCardItem = styled(Card)(() => ({ +export const BaseRadioCardItem = styled(Card)(() => ({ padding: theme.spacing(2), margin: 0, cursor: 'pointer', border: `1px solid ${theme.borders.dark}`, + width: '100%', +})) + +export const DisabledRadioCardItem = styled(BaseRadioCardItem)(() => ({ + opacity: 0.7, + backgroundColor: `${theme.palette.grey[300]} !important`, + pointerEvents: 'none', +})) + +export const SelectedRadioCardItem = styled(BaseRadioCardItem)(() => ({ + backgroundColor: lighten(theme.palette.primary.light, 0.7), })) interface StyledRadioCardItemProps extends CardProps { control: React.ReactNode icon: React.ReactNode + disabled?: boolean selected?: boolean } -// Temporarily here for testing until the components starts being used -export const testRadioOptions: Option[] = [ - { - value: 'card', - label: 'Card', - icon: , - }, - { - value: 'bank', - label: 'Bank', - icon: , - }, - { - value: 'paypal', - label: 'PayPal', - icon: , - }, -] - -function RadioCardItem({ control, icon, selected, ...rest }: StyledRadioCardItemProps) { +function RadioCardItem({ control, icon, selected, disabled, ...rest }: StyledRadioCardItemProps) { + let StyledRadioCardItem = BaseRadioCardItem + if (disabled) { + StyledRadioCardItem = DisabledRadioCardItem + } else if (selected) { + StyledRadioCardItem = SelectedRadioCardItem + } return ( ) => { setValue(event.target.value) } return ( - - + + {options.map((option) => ( - + setValue(option.value)} control={ } label={option.label} /> } icon={option.icon} - selected={field.value === option.value} + selected={field.value === option.value && !option.disabled} + disabled={option.disabled} /> ))} diff --git a/src/components/donation-flow/steps/ChooseAmount.tsx b/src/components/donation-flow/steps/Amount.tsx similarity index 98% rename from src/components/donation-flow/steps/ChooseAmount.tsx rename to src/components/donation-flow/steps/Amount.tsx index 9ff8d801c..3f9c8dabd 100644 --- a/src/components/donation-flow/steps/ChooseAmount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -17,7 +17,7 @@ import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/strip import CheckboxField from 'components/common/form/CheckboxField' import FormSelectField from 'components/common/form/FormSelectField' -const PREFIX = 'FirstStep' +const PREFIX = 'AMOUNT' const classes = { divider: `${PREFIX}-divider`, @@ -30,7 +30,7 @@ const Root = styled('div')(() => ({ }, })) -export default function FirstStep() { +export default function Amount() { const { data: prices } = useSinglePriceList() const { t } = useTranslation('one-time-donation') const mobile = useMediaQuery('(max-width:600px)') diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx new file mode 100644 index 000000000..01ab40fd3 --- /dev/null +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import { styled } from '@mui/material/styles' +import { useFormikContext } from 'formik' + +import { OneTimeDonation } from 'gql/donations' +import RadioCardGroup from '../common/RadioCardGroup' +import CardIcon from '../icons/CardIcon' +import BankIcon from '../icons/BankIcon' + +const PREFIX = 'AMOUNT' + +const classes = { + divider: `${PREFIX}-divider`, +} + +// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed. +const Root = styled('div')(() => ({ + [`& .${classes.divider}`]: { + border: '1px solid #000000', + }, +})) + +export default function PaymentMethod() { + const formik = useFormikContext() + const options = [ + { + value: 'card', + label: 'Card', + icon: , + disabled: !formik.values.amount, + }, + { + value: 'bank', + label: 'Bank Transfer', + icon: , + }, + ] + return ( + + + + ) +} From bab0aea67f6f6be6940904ffe20a5291ad2be2ce Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 16:23:46 +0100 Subject: [PATCH 22/78] 1284 - add the stripe payment intent to the context --- .env.local.example | 2 +- .../donation-flow/DonationFlowContext.tsx | 14 ++++++++++---- .../donation-flow/DonationFlowPage.tsx | 7 ------- src/components/donation-flow/steps/Amount.tsx | 19 ++++++++++++++++++- .../donation-flow/steps/PaymentMethod.tsx | 15 ++++++++++++++- .../stripe/PaymentDetailsStripeForm.tsx | 4 ---- 6 files changed, 43 insertions(+), 18 deletions(-) diff --git a/.env.local.example b/.env.local.example index 8ec1a117f..bd64f394c 100644 --- a/.env.local.example +++ b/.env.local.example @@ -32,7 +32,7 @@ GOOGLE_SECRET= ## Stripe ## ############## -STRIPE_PUBLIC_KEY= +STRIPE_PUBLIC_KEY=pk_test_51HmiW8JLlnbRmnT5ZYwSnviwwLOYo1Y4JdEVmkpzZ6HRQ8f3850Zkn9yLBowIeKytxLu9S24KS2oUU1U5OKPonkB00aGnDOyvK ## Paypal ## ############## diff --git a/src/components/donation-flow/DonationFlowContext.tsx b/src/components/donation-flow/DonationFlowContext.tsx index 4a462290f..bcc5d9bc5 100644 --- a/src/components/donation-flow/DonationFlowContext.tsx +++ b/src/components/donation-flow/DonationFlowContext.tsx @@ -3,10 +3,11 @@ import { useRouter } from 'next/router' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' import { CampaignResponse } from 'gql/campaigns' +import Stripe from 'stripe' type DonationContext = { - stripePaymentIntentId: string - setStripePaymentIntentId: React.Dispatch> + stripePaymentIntent: Stripe.PaymentIntent | null + setStripePaymentIntent: React.Dispatch> campaign: CampaignResponse } @@ -17,9 +18,14 @@ export const DonationFlowProvider = ({ children }: PropsWithChildren) => { const router = useRouter() const slug = String(router.query.slug) const { data, isLoading } = useViewCampaign(slug) - const [stripePaymentIntentId, setStripePaymentIntentId] = React.useState('') + const [stripePaymentIntent, setStripePaymentIntent] = + React.useState(null) if (isLoading || !data) return const { campaign } = data - const value = { stripePaymentIntentId, setStripePaymentIntentId, campaign } + const value = { + stripePaymentIntent, + setStripePaymentIntent, + campaign, + } return {children} } diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 0d0d03065..eb5192f2f 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -67,13 +67,6 @@ const StyledLayout = styled(Layout)(({ theme }) => ({ export default function DonationFlowPage({ slug }: { slug: string }) { const { data, isLoading } = useViewCampaign(slug) const matches = useMediaQuery('sm') - // const paymentIntentMutation = useCreatePaymentIntent({ - // amount: 100, - // currency: 'BGN', - // }) - // useEffect(() => { - // paymentIntentMutation.mutate() - // }, []) if (isLoading || !data) return const { campaign } = data diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 3f9c8dabd..050bca862 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react' +import React, { useContext, useEffect } from 'react' import { Trans, useTranslation } from 'next-i18next' import { useMediaQuery, Box, Collapse, Grid, InputAdornment, Typography } from '@mui/material' import { styled } from '@mui/material/styles' @@ -16,6 +16,8 @@ import FormTextField from 'components/common/form/FormTextField' import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/stripe-fee-calculator' import CheckboxField from 'components/common/form/CheckboxField' import FormSelectField from 'components/common/form/FormSelectField' +import { useCreatePaymentIntent } from 'service/donation' +import { DonationFlowContext } from '../DonationFlowContext' const PREFIX = 'AMOUNT' @@ -32,13 +34,28 @@ const Root = styled('div')(() => ({ export default function Amount() { const { data: prices } = useSinglePriceList() + const DonationContext = useContext(DonationFlowContext) const { t } = useTranslation('one-time-donation') const mobile = useMediaQuery('(max-width:600px)') const [amount] = useField('amount') const [amountWithFees] = useField('amountWithFees') const [amountWithoutFees] = useField('amountWithoutFees') + const paymentIntentMutation = useCreatePaymentIntent({ + amount: Number(amount.value), + currency: 'BGN', + }) + useEffect(() => { + if (amount.value) { + paymentIntentMutation.mutate() + } + }, [amount.value]) + + useEffect(() => { + const intent = paymentIntentMutation.data?.data + DonationContext.setStripePaymentIntent(intent || null) + }, [paymentIntentMutation]) const formik = useFormikContext() useEffect(() => { diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx index 01ab40fd3..88b048e15 100644 --- a/src/components/donation-flow/steps/PaymentMethod.tsx +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useContext } from 'react' import { styled } from '@mui/material/styles' import { useFormikContext } from 'formik' @@ -6,6 +6,8 @@ import { OneTimeDonation } from 'gql/donations' import RadioCardGroup from '../common/RadioCardGroup' import CardIcon from '../icons/CardIcon' import BankIcon from '../icons/BankIcon' +import PaymentDetailsStripeForm from '../stripe/PaymentDetailsStripeForm' +import { DonationFlowContext } from '../DonationFlowContext' const PREFIX = 'AMOUNT' @@ -22,6 +24,10 @@ const Root = styled('div')(() => ({ export default function PaymentMethod() { const formik = useFormikContext() + const DonationContext = useContext(DonationFlowContext) + if (!DonationContext.stripePaymentIntent) { + return null + } const options = [ { value: 'card', @@ -38,6 +44,13 @@ export default function PaymentMethod() { return ( + {DonationContext.stripePaymentIntent ? ( + + ) : ( + 'There is a problem with picking your price. Please try again later.' + )} ) } diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index ce9310288..93e746e46 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -47,10 +47,6 @@ export default function PaymentDetailsStripeForm({ clientSecret, containerProps, }: PaymentDetailsStripeFormProps) { - // const mutation = useCreatePaymentIntent({ amount: 100, currency: Currencies.BGN }) - // useEffect(() => { - // mutation.mutate() - // }, []) return ( Date: Thu, 5 Jan 2023 19:46:22 +0100 Subject: [PATCH 23/78] 1284 - add the stripe key to the runtime config --- next.config.js | 1 + src/components/donation-flow/steps/PaymentMethod.tsx | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/next.config.js b/next.config.js index b5ff66a60..9872f0a3f 100644 --- a/next.config.js +++ b/next.config.js @@ -28,6 +28,7 @@ const moduleExports = { APP_URL: process.env.APP_URL, GTM_ID: process.env.GTM_ID ?? 'GTM-TWQBXM6', PAYPAL_CLIENT_ID: process.env.PAYPAL_CLIENT_ID, + STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY, FEATURE_ENABLED: { CAMPAIGN: process.env.FEATURE_CAMPAIGN ?? false, }, diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx index 88b048e15..f37aa1270 100644 --- a/src/components/donation-flow/steps/PaymentMethod.tsx +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -25,9 +25,6 @@ const Root = styled('div')(() => ({ export default function PaymentMethod() { const formik = useFormikContext() const DonationContext = useContext(DonationFlowContext) - if (!DonationContext.stripePaymentIntent) { - return null - } const options = [ { value: 'card', From 82fab8102228823355186b3483ee269c057b9933 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 5 Jan 2023 19:47:33 +0100 Subject: [PATCH 24/78] 1284 - add locale to the stripe payment process --- .../donation-flow/stripe/PaymentDetailsStripeForm.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index 93e746e46..2f77d4367 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -4,6 +4,7 @@ import { Elements, PaymentElement } from '@stripe/react-stripe-js' import theme from 'common/theme' import { Box, BoxProps } from '@mui/material' +import { useTranslation } from 'react-i18next' const { publicRuntimeConfig: { STRIPE_PUBLIC_KEY }, } = getConfig() @@ -47,12 +48,14 @@ export default function PaymentDetailsStripeForm({ clientSecret, containerProps, }: PaymentDetailsStripeFormProps) { + const { i18n } = useTranslation() return ( From 38438bd1f0c64824275c6423b6a0993608f6b0af Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 12 Jan 2023 20:17:41 +0200 Subject: [PATCH 25/78] dependencies - bump up minor next version to 13.1 --- next.config.js | 14 +++-- package.json | 2 +- yarn.lock | 136 ++++++++++++++++++++++++------------------------- 3 files changed, 75 insertions(+), 77 deletions(-) diff --git a/next.config.js b/next.config.js index 9872f0a3f..f5701d182 100644 --- a/next.config.js +++ b/next.config.js @@ -59,14 +59,12 @@ const moduleExports = { }, ] }, - experimental: { - modularizeImports: { - '@mui/material': { - transform: '@mui/material/{{member}}', - }, - '@mui/icons-material/?(((\\w*)?/?)*)': { - transform: '@mui/icons-material/{{ matches.[1] }}/{{member}}', - }, + modularizeImports: { + '@mui/material': { + transform: '@mui/material/{{member}}', + }, + '@mui/icons-material': { + transform: '@mui/icons-material/{{member}}', }, }, } diff --git a/package.json b/package.json index 5d07672ea..5601d8a3a 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "lodash": "^4.17.21", "mobx": "6.3.2", "mobx-react": "7.2.0", - "next": "^13.0.5", + "next": "^13.1.1", "next-auth": "^4.16.4", "next-i18next": "^13.0.0", "nookies": "^2.5.2", diff --git a/yarn.lock b/yarn.lock index 010118031..cd447a4bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1737,10 +1737,10 @@ __metadata: languageName: node linkType: hard -"@next/env@npm:13.0.5": - version: 13.0.5 - resolution: "@next/env@npm:13.0.5" - checksum: cd005b171d602df1b01415df00e1fee79bfd7c6840f1836bedc7b0f9cb466b73e2e52a6cdcff77535538ef55eec200f5e7accb7670fa3273c625f0e0c3a79afb +"@next/env@npm:13.1.1": + version: 13.1.1 + resolution: "@next/env@npm:13.1.1" + checksum: e265b9b9e7c28023b33ba99b95e84abdfa501507e2f1b7d7b6469ab93d21edbb59fe4378f0557d12864508b747ff1fbc2f9c7de3d704ba51a37c7295fddb0ec1 languageName: node linkType: hard @@ -1760,93 +1760,93 @@ __metadata: languageName: node linkType: hard -"@next/swc-android-arm-eabi@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-android-arm-eabi@npm:13.0.5" +"@next/swc-android-arm-eabi@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-android-arm-eabi@npm:13.1.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@next/swc-android-arm64@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-android-arm64@npm:13.0.5" +"@next/swc-android-arm64@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-android-arm64@npm:13.1.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-arm64@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-darwin-arm64@npm:13.0.5" +"@next/swc-darwin-arm64@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-darwin-arm64@npm:13.1.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@next/swc-darwin-x64@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-darwin-x64@npm:13.0.5" +"@next/swc-darwin-x64@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-darwin-x64@npm:13.1.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@next/swc-freebsd-x64@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-freebsd-x64@npm:13.0.5" +"@next/swc-freebsd-x64@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-freebsd-x64@npm:13.1.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@next/swc-linux-arm-gnueabihf@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-arm-gnueabihf@npm:13.0.5" +"@next/swc-linux-arm-gnueabihf@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-arm-gnueabihf@npm:13.1.1" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@next/swc-linux-arm64-gnu@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-arm64-gnu@npm:13.0.5" +"@next/swc-linux-arm64-gnu@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-arm64-gnu@npm:13.1.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-arm64-musl@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-arm64-musl@npm:13.0.5" +"@next/swc-linux-arm64-musl@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-arm64-musl@npm:13.1.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@next/swc-linux-x64-gnu@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-x64-gnu@npm:13.0.5" +"@next/swc-linux-x64-gnu@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-x64-gnu@npm:13.1.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@next/swc-linux-x64-musl@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-linux-x64-musl@npm:13.0.5" +"@next/swc-linux-x64-musl@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-linux-x64-musl@npm:13.1.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@next/swc-win32-arm64-msvc@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-win32-arm64-msvc@npm:13.0.5" +"@next/swc-win32-arm64-msvc@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-win32-arm64-msvc@npm:13.1.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@next/swc-win32-ia32-msvc@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-win32-ia32-msvc@npm:13.0.5" +"@next/swc-win32-ia32-msvc@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-win32-ia32-msvc@npm:13.1.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@next/swc-win32-x64-msvc@npm:13.0.5": - version: 13.0.5 - resolution: "@next/swc-win32-x64-msvc@npm:13.0.5" +"@next/swc-win32-x64-msvc@npm:13.1.1": + version: 13.1.1 + resolution: "@next/swc-win32-x64-msvc@npm:13.1.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -8090,28 +8090,28 @@ __metadata: languageName: node linkType: hard -"next@npm:^13.0.5": - version: 13.0.5 - resolution: "next@npm:13.0.5" - dependencies: - "@next/env": 13.0.5 - "@next/swc-android-arm-eabi": 13.0.5 - "@next/swc-android-arm64": 13.0.5 - "@next/swc-darwin-arm64": 13.0.5 - "@next/swc-darwin-x64": 13.0.5 - "@next/swc-freebsd-x64": 13.0.5 - "@next/swc-linux-arm-gnueabihf": 13.0.5 - "@next/swc-linux-arm64-gnu": 13.0.5 - "@next/swc-linux-arm64-musl": 13.0.5 - "@next/swc-linux-x64-gnu": 13.0.5 - "@next/swc-linux-x64-musl": 13.0.5 - "@next/swc-win32-arm64-msvc": 13.0.5 - "@next/swc-win32-ia32-msvc": 13.0.5 - "@next/swc-win32-x64-msvc": 13.0.5 +"next@npm:^13.1.1": + version: 13.1.1 + resolution: "next@npm:13.1.1" + dependencies: + "@next/env": 13.1.1 + "@next/swc-android-arm-eabi": 13.1.1 + "@next/swc-android-arm64": 13.1.1 + "@next/swc-darwin-arm64": 13.1.1 + "@next/swc-darwin-x64": 13.1.1 + "@next/swc-freebsd-x64": 13.1.1 + "@next/swc-linux-arm-gnueabihf": 13.1.1 + "@next/swc-linux-arm64-gnu": 13.1.1 + "@next/swc-linux-arm64-musl": 13.1.1 + "@next/swc-linux-x64-gnu": 13.1.1 + "@next/swc-linux-x64-musl": 13.1.1 + "@next/swc-win32-arm64-msvc": 13.1.1 + "@next/swc-win32-ia32-msvc": 13.1.1 + "@next/swc-win32-x64-msvc": 13.1.1 "@swc/helpers": 0.4.14 caniuse-lite: ^1.0.30001406 postcss: 8.4.14 - styled-jsx: 5.1.0 + styled-jsx: 5.1.1 peerDependencies: fibers: ">= 3.1.0" node-sass: ^6.0.0 || ^7.0.0 @@ -8154,7 +8154,7 @@ __metadata: optional: true bin: next: dist/bin/next - checksum: e13d8c51153b975d40002a222260352c8be048792014dd322f9a44bb9050a8f0b34c9e5c62329b3439febd87aacb6da5e0377dafff82570e79ba4f9d878f3ec5 + checksum: 97a9154d620770388e6a0a84982b094172b851f49b408ac53d83fc4110470dbef63225b9c0008e223c60759b5aa606a547bd909b15d6a166526e168877dfc802 languageName: node linkType: hard @@ -8857,7 +8857,7 @@ __metadata: lodash: ^4.17.21 mobx: 6.3.2 mobx-react: 7.2.0 - next: ^13.0.5 + next: ^13.1.1 next-auth: ^4.16.4 next-i18next: ^13.0.0 next-sitemap: ^2.5.20 @@ -10503,9 +10503,9 @@ __metadata: languageName: node linkType: hard -"styled-jsx@npm:5.1.0": - version: 5.1.0 - resolution: "styled-jsx@npm:5.1.0" +"styled-jsx@npm:5.1.1": + version: 5.1.1 + resolution: "styled-jsx@npm:5.1.1" dependencies: client-only: 0.0.1 peerDependencies: @@ -10515,7 +10515,7 @@ __metadata: optional: true babel-plugin-macros: optional: true - checksum: e5b70476fd9059147dfe35dd912e537e422a7f900cc88f80456c97da495c655598875d64de2199641d627770a7e55ed08be0fd82646bc386791fdb1d0e5af2b1 + checksum: 523a33b38603492547e861b98e29c873939b04e15fbe5ef16132c6f1e15958126647983c7d4675325038b428a5e91183d996e90141b18bdd1bbadf6e2c45b2fa languageName: node linkType: hard From cab85de7971948b070adf8e8fa501d6e1a171939 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Thu, 12 Jan 2023 20:41:00 +0200 Subject: [PATCH 26/78] 1284 - add step splitter component --- .../donation-flow/DonationFlowForm.tsx | 4 ++ .../donation-flow/common/StepSplitter.tsx | 37 +++++++++++++++++++ src/components/donation-flow/steps/Amount.tsx | 8 ++++ 3 files changed, 49 insertions(+) create mode 100644 src/components/donation-flow/common/StepSplitter.tsx diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index e3daf5b9c..e7aa5c2e6 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -6,6 +6,7 @@ import { CardRegion } from 'gql/donations.enums' import SubmitButton from 'components/common/form/SubmitButton' import Amount from './steps/Amount' import PaymentMethod from './steps/PaymentMethod' +import StepSplitter from './common/StepSplitter' const initialValues = { amount: '', @@ -52,7 +53,10 @@ export function DonationFlowForm() { }} autoComplete="off"> + + + Submit )} diff --git a/src/components/donation-flow/common/StepSplitter.tsx b/src/components/donation-flow/common/StepSplitter.tsx new file mode 100644 index 000000000..75c356e12 --- /dev/null +++ b/src/components/donation-flow/common/StepSplitter.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import { Avatar, Box, Typography, Unstable_Grid2 as Grid2 } from '@mui/material' +import { styled } from '@mui/styles' +import theme from 'common/theme' +import { grey } from '@mui/material/colors' + +type StepSplitterProps = { + content: string + active?: boolean +} + +const StyledLine = styled('div')(() => ({ + width: '100%', + height: '1px', + backgroundColor: grey[400], + margin: '0 0 0 0', +})) + +function StepSplitter({ content, active }: StepSplitterProps) { + return ( + + + + {content} + + + + ) +} + +export default StepSplitter diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 050bca862..514be6620 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -174,6 +174,14 @@ export default function Amount() { totalAmount: moneyPublicDecimals2(amountWithFees.value), }} /> + + {t('third-step.recurring-donation')} + } + /> + ) : null}
From 606385cba436dd8b98957b601c97a6e2251c197b Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 02:30:19 +0100 Subject: [PATCH 27/78] 1284 - add mobile view for the payment method --- README.md | 8 ++-- .../donation-flow/DonationFlowForm.tsx | 4 +- .../donation-flow/common/StepSplitter.tsx | 5 +-- .../donation-flow/steps/PaymentMethod.tsx | 43 ++++++++++++++++--- .../stripe/PaymentDetailsStripeForm.tsx | 2 +- 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 44c21b36b..980cbc495 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ ## Perequisites - Node.js - - Installation + - Installation - [Windows / MacOS](https://nodejs.org/en/download/) - [Debian and Ubuntu based Linux distributions](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) ```shell @@ -46,10 +46,10 @@ - Installation https://yarnpkg.com/getting-started/install ```shell corepack enable - yarn set version berry + yarn set version berry ``` - make sure `cmdtest` is not installed, it has a different `yarn` command - + ## Initial setup ```shell @@ -98,7 +98,9 @@ Watch releases of this repository to be notified about future updates: ## Contributors ✨ + [![All Contributors](https://img.shields.io/badge/all_contributors-64-orange.svg?style=flat-square)](#contributors-) + Please check [contributors guide](https://github.com/podkrepi-bg/frontend/blob/master/CONTRIBUTING.md) for: diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index e7aa5c2e6..469e942bb 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -52,10 +52,10 @@ export function DonationFlowForm() { marginRight: 'auto', }} autoComplete="off"> - - + + Submit diff --git a/src/components/donation-flow/common/StepSplitter.tsx b/src/components/donation-flow/common/StepSplitter.tsx index 75c356e12..067bb4051 100644 --- a/src/components/donation-flow/common/StepSplitter.tsx +++ b/src/components/donation-flow/common/StepSplitter.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Avatar, Box, Typography, Unstable_Grid2 as Grid2 } from '@mui/material' +import { Avatar, Box, Typography } from '@mui/material' import { styled } from '@mui/styles' import theme from 'common/theme' import { grey } from '@mui/material/colors' @@ -13,12 +13,11 @@ const StyledLine = styled('div')(() => ({ width: '100%', height: '1px', backgroundColor: grey[400], - margin: '0 0 0 0', })) function StepSplitter({ content, active }: StepSplitterProps) { return ( - + ({ export default function PaymentMethod() { const formik = useFormikContext() const DonationContext = useContext(DonationFlowContext) + const { small } = useMobile() const options = [ { value: 'card', @@ -38,15 +41,43 @@ export default function PaymentMethod() { icon: , }, ] + const mobileOptions = [ + { + value: 'card', + label: 'Card', + icon: , + disabled: !formik.values.amount, + content: ( + <> + {DonationContext.stripePaymentIntent ? ( + + ) : null} + + ), + }, + { + value: 'bank', + label: 'Bank Transfer', + icon: , + content: <>TODO: Add Bank Transfer Content, + }, + ] + return ( - - {DonationContext.stripePaymentIntent ? ( - + {small ? ( + ) : ( - 'There is a problem with picking your price. Please try again later.' + <> + + {DonationContext.stripePaymentIntent ? ( + + ) : null} + )} ) diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index 2f77d4367..c8d8b2ee7 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -18,7 +18,7 @@ const appearance: Appearance = { colorDanger: theme.palette.error.main, fontFamily: "Montserrat, 'Helvetica Neue', Helvetica, Arial, sans-serif", fontSizeSm: theme.typography.pxToRem(12), - fontSizeBase: theme.typography.pxToRem(16), + fontSizeBase: theme.typography.pxToRem(14), fontSizeLg: theme.typography.pxToRem(18), fontSizeXl: theme.typography.pxToRem(20), spacingUnit: theme.spacing(0), From 1ac484a164551d4aaa8e22a9ce35429b9ee338d7 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 02:38:55 +0100 Subject: [PATCH 28/78] 1284 - add disabled and fix styling for the RadioAccordionGroup --- .../common/RadioAccordionGroup.tsx | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 7c14b772e..dec96d795 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -2,65 +2,58 @@ import React from 'react' import { Box, BoxProps, - Button, Collapse, FormControl, FormControlLabel, Radio, RadioGroup, RadioGroupProps, - TextField, } from '@mui/material' import { styled } from '@mui/material/styles' import { useField } from 'formik' import theme from 'common/theme' -import CardIcon from '../icons/CardIcon' -import BankIcon from '../icons/BankIcon' -export const StyledRadioAccordionItem = styled(Box)(() => ({ +export const BaseRadioAccordionItem = styled(Box)(() => ({ '&:not(:last-child)': { borderBottom: `1px solid ${theme.borders.dark}`, + borderTopLeftRadius: theme.borders.semiRound, + borderTopRightRadius: theme.borders.semiRound, + }, + '&:last-child': { + borderBottomLeftRadius: theme.borders.semiRound, + borderBottomRightRadius: theme.borders.semiRound, }, padding: theme.spacing(2), margin: 0, cursor: 'pointer', })) +export const DisabledRadioAccordionItem = styled(BaseRadioAccordionItem)(() => ({ + opacity: 0.7, + backgroundColor: `${theme.palette.grey[300]} !important`, + pointerEvents: 'none', +})) + interface RadioAccordionItemProps extends BoxProps { control: React.ReactNode icon: React.ReactNode content?: React.ReactNode selected?: boolean + disabled?: boolean } -// Temporarily here for testing until the components starts being used -export const testRadioOptions: Option[] = [ - { - value: 'card', - label: 'Card', - content: ( -
- - -
- ), - icon: , - }, - { - value: 'bank', - label: 'Bank', - content:
TODO: Add bank form
, - icon: , - }, -] - function RadioAccordionItem({ control, icon, selected, content, + disabled, ...rest }: RadioAccordionItemProps) { + let StyledRadioAccordionItem = BaseRadioAccordionItem + if (disabled) { + StyledRadioAccordionItem = DisabledRadioAccordionItem + } return ( @@ -79,6 +72,7 @@ type Option = { label: string content: React.ReactNode icon: React.ReactNode + disabled?: boolean } export interface RadioAccordionGroupProps extends RadioGroupProps { @@ -93,7 +87,11 @@ function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProp } return ( - + setValue(option.value)} control={ - } label={option.label} /> + } + label={option.label} + disabled={option.disabled} + /> } icon={option.icon} selected={field.value === option.value} content={option.content} + disabled={option.disabled} /> ))} From b0febae80732d17eea7ecef761570980f04576bc Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 21:01:16 +0100 Subject: [PATCH 29/78] 1284 - rewrite DonationFlowPage styles to use the new `styled` api --- .../donation-flow/DonationFlowPage.tsx | 84 +++++++++++-------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index eb5192f2f..8a4ea7013 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -1,7 +1,7 @@ import Link from 'next/link' import Image from 'next/image' import { styled } from '@mui/material/styles' -import { Box, Grid, Typography, useMediaQuery } from '@mui/material' +import { Box, Unstable_Grid2 as Grid2, Typography, useMediaQuery } from '@mui/material' import theme from 'common/theme' import { routes } from 'common/routes' @@ -12,11 +12,9 @@ import { import Layout from 'components/layout/Layout' import { useViewCampaign } from 'common/hooks/campaigns' import CenteredSpinner from 'components/common/CenteredSpinner' -import ChooseAmount from './steps/Amount' + import { DonationFlowForm } from './DonationFlowForm' import { DonationFlowProvider } from './DonationFlowContext' -// import RadioAccordionGroup, { testRadioOptions } from 'components/donation-flow/RadioAccordionGroup' -// import PaymentDetailsStripeForm from 'components/donations/stripe/PaymentDetailsStripeForm' const PREFIX = 'OneTimeDonationPage' @@ -28,6 +26,43 @@ const classes = { stepperWrapper: `${PREFIX}-stepperWrapper`, } +const StyledBannerWrapper = styled(Box)(() => ({ + '& span': { + position: 'inherit !important', + }, +})) + +const StyledBanner = styled(Image)(({ theme }) => ({ + zIndex: -1, + maxHeight: '350px !important', + marginTop: `${theme.spacing(10)} !important`, + [theme.breakpoints.up('md')]: { + marginTop: `${theme.spacing(14)} !important`, + }, + objectFit: 'cover', +})) + +const StyledBeneficiaryAvatarWrapper = styled(Grid2)(({ theme }) => ({ + textAlign: 'center', + [theme.breakpoints.up('md')]: { + textAlign: 'center', + }, +})) + +const StyledBeneficiaryAvatar = styled(Image)(({ theme }) => ({ + borderRadius: '50%', + border: `4px solid ${theme.palette.common.white} !important`, + textAlign: 'center', + [theme.breakpoints.up('md')]: { + border: `4px solid ${theme.palette.common.white} !important`, + }, +})) + +const StyledStepperWrapper = styled(Grid2)(({ theme }) => ({ + gap: theme.spacing(2), + display: 'grid', +})) + const StyledLayout = styled(Layout)(({ theme }) => ({ [`& .${classes.bannerWrapper}`]: { '& span': { @@ -76,40 +111,28 @@ export default function DonationFlowPage({ slug }: { slug: string }) { return ( - - + {/* A11Y TODO: Translate alt text */} - Campaign banner image - - - Campaign banner image + + + - + - + - {/* {paymentIntentMutation.isLoading ? ( - - ) : ( - - )} */} - {/* */} - - + +
) From e21ec83f5ea4e0d140224ec056955c1da32b5390 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 21:11:47 +0100 Subject: [PATCH 30/78] 1284 - remove unecessary `StyledLayout` --- .../donation-flow/DonationFlowPage.tsx | 50 +------------------ 1 file changed, 2 insertions(+), 48 deletions(-) diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 8a4ea7013..2d8787de7 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -16,16 +16,6 @@ import CenteredSpinner from 'components/common/CenteredSpinner' import { DonationFlowForm } from './DonationFlowForm' import { DonationFlowProvider } from './DonationFlowContext' -const PREFIX = 'OneTimeDonationPage' - -const classes = { - bannerWrapper: `${PREFIX}-bannerWrapper`, - banner: `${PREFIX}-banner`, - beneficiaryAvatarWrapper: `${PREFIX}-beneficiaryAvatarWrapper`, - beneficiaryAvatar: `${PREFIX}-beneficiaryAvatar`, - stepperWrapper: `${PREFIX}-stepperWrapper`, -} - const StyledBannerWrapper = styled(Box)(() => ({ '& span': { position: 'inherit !important', @@ -63,42 +53,6 @@ const StyledStepperWrapper = styled(Grid2)(({ theme }) => ({ display: 'grid', })) -const StyledLayout = styled(Layout)(({ theme }) => ({ - [`& .${classes.bannerWrapper}`]: { - '& span': { - position: 'inherit !important', - }, - }, - - [`& .${classes.banner}`]: { - zIndex: -1, - maxHeight: '350px !important', - marginTop: `${theme.spacing(10)} !important`, - [theme.breakpoints.up('md')]: { - marginTop: `${theme.spacing(14)} !important`, - }, - objectFit: 'cover', - }, - - [`& .${classes.beneficiaryAvatarWrapper}`]: { - textAlign: 'center', - [theme.breakpoints.up('md')]: { - textAlign: 'center', - }, - }, - - [`& .${classes.beneficiaryAvatar}`]: { - borderRadius: '50%', - border: `4px solid ${theme.palette.common.white} !important`, - textAlign: 'center', - }, - - [`& .${classes.stepperWrapper}`]: { - gap: theme.spacing(2), - display: 'grid', - }, -})) - export default function DonationFlowPage({ slug }: { slug: string }) { const { data, isLoading } = useViewCampaign(slug) const matches = useMediaQuery('sm') @@ -110,7 +64,7 @@ export default function DonationFlowPage({ slug }: { slug: string }) { return ( - + - + ) } From 3696102530a46eb49f4824d556177a3e6c26b546 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 21:31:13 +0100 Subject: [PATCH 31/78] 1284 - move tax calculations and checkbox to the PaymentMethod step --- .../donation-flow/DonationFlowPage.tsx | 2 +- src/components/donation-flow/steps/Amount.tsx | 51 +---------- .../donation-flow/steps/PaymentMethod.tsx | 89 +++++++++++++++---- .../stripe/PaymentDetailsStripeForm.tsx | 3 +- 4 files changed, 75 insertions(+), 70 deletions(-) diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 2d8787de7..0c00bb214 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -9,8 +9,8 @@ import { backgroundCampaignPictureUrl, beneficiaryCampaignPictureUrl, } from 'common/util/campaignImageUrls' -import Layout from 'components/layout/Layout' import { useViewCampaign } from 'common/hooks/campaigns' +import Layout from 'components/layout/Layout' import CenteredSpinner from 'components/common/CenteredSpinner' import { DonationFlowForm } from './DonationFlowForm' diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 514be6620..3d19652dc 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -1,21 +1,19 @@ import React, { useContext, useEffect } from 'react' -import { Trans, useTranslation } from 'next-i18next' +import { useTranslation } from 'next-i18next' import { useMediaQuery, Box, Collapse, Grid, InputAdornment, Typography } from '@mui/material' import { styled } from '@mui/material/styles' import { useField, useFormikContext } from 'formik' import { OneTimeDonation } from 'gql/donations' -import { CardRegion } from 'gql/donations.enums' import theme from 'common/theme' import { useSinglePriceList } from 'common/hooks/donation' -import { moneyPublic, moneyPublicDecimals2, toMoney } from 'common/util/money' +import { moneyPublic, toMoney } from 'common/util/money' import RadioButtonGroup from 'components/common/form/RadioButtonGroup' import FormTextField from 'components/common/form/FormTextField' import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/stripe-fee-calculator' import CheckboxField from 'components/common/form/CheckboxField' -import FormSelectField from 'components/common/form/FormSelectField' import { useCreatePaymentIntent } from 'service/donation' import { DonationFlowContext } from '../DonationFlowContext' @@ -39,8 +37,6 @@ export default function Amount() { const mobile = useMediaQuery('(max-width:600px)') const [amount] = useField('amount') - const [amountWithFees] = useField('amountWithFees') - const [amountWithoutFees] = useField('amountWithoutFees') const paymentIntentMutation = useCreatePaymentIntent({ amount: Number(amount.value), currency: 'BGN', @@ -131,49 +127,6 @@ export default function Amount() { {amount.value ? ( - - - {t('third-step.card-include-fees')} - } - /> - - - - - - { + const { t } = useTranslation('one-time-donation') + const [amountWithFees] = useField('amountWithFees') + const [amountWithoutFees] = useField('amountWithoutFees') + return ( + <> + + + {t('third-step.card-include-fees')}} + /> +
+ + + + + + + ) } - -// TODO jss-to-styled codemod: The Fragment root was replaced by div. Change the tag if needed. -const Root = styled('div')(() => ({ - [`& .${classes.divider}`]: { - border: '1px solid #000000', - }, -})) - export default function PaymentMethod() { const formik = useFormikContext() const DonationContext = useContext(DonationFlowContext) const { small } = useMobile() + const [payment] = useField('payment') const options = [ { value: 'card', @@ -66,19 +111,25 @@ export default function PaymentMethod() { ] return ( - + {small ? ( ) : ( <> - {DonationContext.stripePaymentIntent ? ( + {DonationContext.stripePaymentIntent && payment.value === 'card' ? ( - ) : null} + ) : ( + + + + )} + )} - + ) } diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index c8d8b2ee7..f945d9ef4 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -39,6 +39,7 @@ const appearance: Appearance = { } const stripePromise = loadStripe(STRIPE_PUBLIC_KEY) +console.log('loadStripe') export type PaymentDetailsStripeFormProps = { clientSecret: string @@ -58,7 +59,7 @@ export default function PaymentDetailsStripeForm({ locale: i18n.language, }}> - + ) From 8466b520cd337bab72bff74415b7b5cd9b5b371a Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 22:10:59 +0100 Subject: [PATCH 32/78] 1284 - fix stripe promise and styling issues --- .../donation-flow/DonationFlowForm.tsx | 45 ++++++++++++------- .../donation-flow/common/StepSplitter.tsx | 15 +++---- src/components/donation-flow/steps/Amount.tsx | 3 +- .../donation-flow/steps/PaymentMethod.tsx | 33 +++++++------- .../stripe/PaymentDetailsStripeForm.tsx | 21 ++++----- src/gql/donations.d.ts | 2 +- src/pages/_app.tsx | 9 +++- 7 files changed, 73 insertions(+), 55 deletions(-) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 469e942bb..8b9e0d584 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -1,27 +1,31 @@ import React from 'react' import * as yup from 'yup' import { Form, Formik } from 'formik' +import { Alert, Hidden, Unstable_Grid2 as Grid2 } from '@mui/material' + import { FirstStep } from 'gql/donations' import { CardRegion } from 'gql/donations.enums' import SubmitButton from 'components/common/form/SubmitButton' + import Amount from './steps/Amount' import PaymentMethod from './steps/PaymentMethod' import StepSplitter from './common/StepSplitter' const initialValues = { amount: '', - payment: 'card', + payment: '', amountWithFees: 0, cardIncludeFees: false, cardRegion: CardRegion.EU, otherAmount: 0, } +//TODO: Should be a SchemaOf the whole form export const validationSchema: yup.SchemaOf = yup .object() .defined() .shape({ - payment: yup.string().required().oneOf(['card', 'bank']), + payment: yup.string().oneOf(['card', 'bank']), amount: yup.string().when('payment', { is: 'card', // Here we should fetch the possible payments to put into the oneOf, but it's not that important @@ -44,21 +48,30 @@ export function DonationFlowForm() { validateOnMount validateOnBlur> {({ handleSubmit }) => ( -
- - - - + + + + + + + - Submit - + Submit + + + + + TODO: Alerts row here + + + )} ) diff --git a/src/components/donation-flow/common/StepSplitter.tsx b/src/components/donation-flow/common/StepSplitter.tsx index 067bb4051..7aacaf7dd 100644 --- a/src/components/donation-flow/common/StepSplitter.tsx +++ b/src/components/donation-flow/common/StepSplitter.tsx @@ -1,24 +1,23 @@ import React from 'react' import { Avatar, Box, Typography } from '@mui/material' import { styled } from '@mui/styles' -import theme from 'common/theme' import { grey } from '@mui/material/colors' +import theme from 'common/theme' + type StepSplitterProps = { content: string active?: boolean } -const StyledLine = styled('div')(() => ({ - width: '100%', - height: '1px', - backgroundColor: grey[400], -})) +const Line = () => { + return +} function StepSplitter({ content, active }: StepSplitterProps) { return ( - + {content} - + ) } diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 3d19652dc..f4af717b5 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -12,9 +12,10 @@ import { moneyPublic, toMoney } from 'common/util/money' import RadioButtonGroup from 'components/common/form/RadioButtonGroup' import FormTextField from 'components/common/form/FormTextField' -import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/stripe-fee-calculator' import CheckboxField from 'components/common/form/CheckboxField' import { useCreatePaymentIntent } from 'service/donation' + +import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/stripe-fee-calculator' import { DonationFlowContext } from '../DonationFlowContext' const PREFIX = 'AMOUNT' diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx index fbba9ac2e..7a9f1d474 100644 --- a/src/components/donation-flow/steps/PaymentMethod.tsx +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -1,11 +1,12 @@ import React, { useContext } from 'react' import { Trans, useTranslation } from 'react-i18next' -import { Box, Typography, Unstable_Grid2 as Grid2 } from '@mui/material' +import { Box, CircularProgress, Typography, Unstable_Grid2 as Grid2 } from '@mui/material' import { useField, useFormikContext } from 'formik' import { CardRegion } from 'gql/donations.enums' import { OneTimeDonation } from 'gql/donations' import useMobile from 'common/hooks/useMobile' +import { moneyPublicDecimals2 } from 'common/util/money' import CheckboxField from 'components/common/form/CheckboxField' import FormSelectField from 'components/common/form/FormSelectField' @@ -15,8 +16,6 @@ import CardIcon from '../icons/CardIcon' import BankIcon from '../icons/BankIcon' import PaymentDetailsStripeForm from '../stripe/PaymentDetailsStripeForm' import { DonationFlowContext } from '../DonationFlowContext' -import { moneyPublicDecimals2 } from 'common/util/money' -import CenteredSpinner from 'components/common/CenteredSpinner' const TaxesCheckbox = () => { const { t } = useTranslation('one-time-donation') @@ -68,6 +67,7 @@ const TaxesCheckbox = () => { ) } + export default function PaymentMethod() { const formik = useFormikContext() const DonationContext = useContext(DonationFlowContext) @@ -109,7 +109,6 @@ export default function PaymentMethod() { content: <>TODO: Add Bank Transfer Content, }, ] - return ( {small ? ( @@ -117,17 +116,21 @@ export default function PaymentMethod() { ) : ( <> - {DonationContext.stripePaymentIntent && payment.value === 'card' ? ( - - ) : ( - - - - )} - + {payment.value === 'card' ? ( + DonationContext.stripePaymentIntent ? ( + <> + + + + ) : ( + + + + ) + ) : null} )} diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index f945d9ef4..971940967 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -1,23 +1,21 @@ -import getConfig from 'next/config' -import { Appearance, loadStripe } from '@stripe/stripe-js' +import { Appearance } from '@stripe/stripe-js' import { Elements, PaymentElement } from '@stripe/react-stripe-js' - -import theme from 'common/theme' import { Box, BoxProps } from '@mui/material' import { useTranslation } from 'react-i18next' -const { - publicRuntimeConfig: { STRIPE_PUBLIC_KEY }, -} = getConfig() + +import theme from 'common/theme' +import { stripePromise } from 'pages/_app' const appearance: Appearance = { theme: 'stripe', variables: { colorPrimary: theme.palette.primary.main, colorBackground: theme.palette.background.paper, - colorText: theme.palette.text.primary, + // colorText: theme.palette.text.primary resolves to rgba(0, 0, 0, 0.87) and Stripe doesn't like that + colorText: 'rgb(0, 0, 0)', colorDanger: theme.palette.error.main, fontFamily: "Montserrat, 'Helvetica Neue', Helvetica, Arial, sans-serif", - fontSizeSm: theme.typography.pxToRem(12), + fontSizeSm: theme.typography.pxToRem(14), fontSizeBase: theme.typography.pxToRem(14), fontSizeLg: theme.typography.pxToRem(18), fontSizeXl: theme.typography.pxToRem(20), @@ -38,9 +36,6 @@ const appearance: Appearance = { }, } -const stripePromise = loadStripe(STRIPE_PUBLIC_KEY) -console.log('loadStripe') - export type PaymentDetailsStripeFormProps = { clientSecret: string containerProps?: BoxProps @@ -59,7 +54,7 @@ export default function PaymentDetailsStripeForm({ locale: i18n.language, }}> - + ) diff --git a/src/gql/donations.d.ts b/src/gql/donations.d.ts index 866096ced..bc68b2b7b 100644 --- a/src/gql/donations.d.ts +++ b/src/gql/donations.d.ts @@ -142,7 +142,7 @@ export type DonationStep = { } export type FirstStep = { - payment: string + payment?: string amount?: string } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 2ad66eea1..5be9a4ac1 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -8,6 +8,8 @@ import { AppProps } from 'next/app' import Head from 'next/head' import { useRouter } from 'next/router' import { useEffect, useState } from 'react' +import getConfig from 'next/config' +import { loadStripe } from '@stripe/stripe-js' import createEmotionCache from 'common/createEmotionCache' import theme from 'common/theme' @@ -19,10 +21,15 @@ import 'styles/global.scss' import { Provider } from 'mobx-react' import { stores } from 'stores/DomainStores/stores' - +const { + publicRuntimeConfig: { STRIPE_PUBLIC_KEY }, +} = getConfig() // Client-side cache, shared for the whole session of the user in the browser. const clientSideEmotionCache = createEmotionCache() +// Stripe promise needed for stripe elements +export const stripePromise = loadStripe(STRIPE_PUBLIC_KEY) + declare module '@mui/styles/defaultTheme' { // eslint-disable-next-line @typescript-eslint/no-empty-interface interface DefaultTheme extends Theme {} From 061f53a643e4622d90d21e2f2059c68ea166068f Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 22:26:08 +0100 Subject: [PATCH 33/78] 1284 - fix mobile and tablet styling --- .../donation-flow/DonationFlowForm.tsx | 2 +- .../donation-flow/DonationFlowPage.tsx | 10 +++---- .../donation-flow/steps/PaymentMethod.tsx | 30 ++++++++----------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 8b9e0d584..9b4916cc1 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -48,7 +48,7 @@ export function DonationFlowForm() { validateOnMount validateOnBlur> {({ handleSubmit }) => ( - +
({ }, })) -const StyledStepperWrapper = styled(Grid2)(({ theme }) => ({ - gap: theme.spacing(2), - display: 'grid', +const StyledStepsWrapper = styled(Grid2)(() => ({ + width: '100%', + maxWidth: '960px', })) export default function DonationFlowPage({ slug }: { slug: string }) { @@ -86,7 +86,7 @@ export default function DonationFlowPage({ slug }: { slug: string }) { /> - + - + diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx index 7a9f1d474..68245ecdf 100644 --- a/src/components/donation-flow/steps/PaymentMethod.tsx +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -1,11 +1,11 @@ import React, { useContext } from 'react' import { Trans, useTranslation } from 'react-i18next' -import { Box, CircularProgress, Typography, Unstable_Grid2 as Grid2 } from '@mui/material' +import { Box, Typography, Unstable_Grid2 as Grid2, useMediaQuery } from '@mui/material' import { useField, useFormikContext } from 'formik' import { CardRegion } from 'gql/donations.enums' import { OneTimeDonation } from 'gql/donations' -import useMobile from 'common/hooks/useMobile' +import theme from 'common/theme' import { moneyPublicDecimals2 } from 'common/util/money' import CheckboxField from 'components/common/form/CheckboxField' import FormSelectField from 'components/common/form/FormSelectField' @@ -71,7 +71,7 @@ const TaxesCheckbox = () => { export default function PaymentMethod() { const formik = useFormikContext() const DonationContext = useContext(DonationFlowContext) - const { small } = useMobile() + const isSmall = useMediaQuery(theme.breakpoints.down('md')) const [payment] = useField('payment') const options = [ { @@ -111,25 +111,19 @@ export default function PaymentMethod() { ] return ( - {small ? ( + {isSmall ? ( ) : ( <> - {payment.value === 'card' ? ( - DonationContext.stripePaymentIntent ? ( - <> - - - - ) : ( - - - - ) + {payment.value === 'card' && DonationContext.stripePaymentIntent ? ( + <> + + + ) : null} )} From 1c3d241a5dd0a2caa44eb4b4ee3caa15c8b1ea0d Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 13 Jan 2023 22:41:10 +0100 Subject: [PATCH 34/78] 1284 - remove stripe public key from the .env example file --- .env.local.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.local.example b/.env.local.example index bd64f394c..8ec1a117f 100644 --- a/.env.local.example +++ b/.env.local.example @@ -32,7 +32,7 @@ GOOGLE_SECRET= ## Stripe ## ############## -STRIPE_PUBLIC_KEY=pk_test_51HmiW8JLlnbRmnT5ZYwSnviwwLOYo1Y4JdEVmkpzZ6HRQ8f3850Zkn9yLBowIeKytxLu9S24KS2oUU1U5OKPonkB00aGnDOyvK +STRIPE_PUBLIC_KEY= ## Paypal ## ############## From 64b0454d32de0c5112f4e4e5ebe1ad16443b0f91 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Mon, 16 Jan 2023 14:21:10 +0100 Subject: [PATCH 35/78] 1284 - add `` for email on the stripe payment --- .../stripe/PaymentDetailsStripeForm.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx index 971940967..35da55599 100644 --- a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx @@ -1,10 +1,11 @@ +import { useSession } from 'next-auth/react' +import { useTranslation } from 'react-i18next' import { Appearance } from '@stripe/stripe-js' -import { Elements, PaymentElement } from '@stripe/react-stripe-js' +import { Elements, LinkAuthenticationElement, PaymentElement } from '@stripe/react-stripe-js' import { Box, BoxProps } from '@mui/material' -import { useTranslation } from 'react-i18next' -import theme from 'common/theme' import { stripePromise } from 'pages/_app' +import theme from 'common/theme' const appearance: Appearance = { theme: 'stripe', @@ -45,6 +46,8 @@ export default function PaymentDetailsStripeForm({ containerProps, }: PaymentDetailsStripeFormProps) { const { i18n } = useTranslation() + // use session to get the email + const { data: session } = useSession() return ( + From f7d8b3f0500222cd68d6e0edb3e38b0fa8016862 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Mon, 16 Jan 2023 15:21:25 +0100 Subject: [PATCH 36/78] 1284 - fix rerendering issue on RadioCardItems --- .../donation-flow/common/RadioCardGroup.tsx | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index f4506e8c7..688548487 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -14,22 +14,17 @@ import { styled, lighten } from '@mui/material/styles' import theme from 'common/theme' import { useField } from 'formik' -export const BaseRadioCardItem = styled(Card)(() => ({ +export const StyledRadioCardItem = styled(Card)(() => ({ padding: theme.spacing(2), margin: 0, cursor: 'pointer', border: `1px solid ${theme.borders.dark}`, width: '100%', -})) - -export const DisabledRadioCardItem = styled(BaseRadioCardItem)(() => ({ - opacity: 0.7, - backgroundColor: `${theme.palette.grey[300]} !important`, - pointerEvents: 'none', -})) - -export const SelectedRadioCardItem = styled(BaseRadioCardItem)(() => ({ - backgroundColor: lighten(theme.palette.primary.light, 0.7), + // add black outline to selected card + '&:focus': { + outline: 'none', + border: `1px solid ${theme.palette.primary.main}`, + }, })) interface StyledRadioCardItemProps extends CardProps { @@ -40,16 +35,24 @@ interface StyledRadioCardItemProps extends CardProps { } function RadioCardItem({ control, icon, selected, disabled, ...rest }: StyledRadioCardItemProps) { - let StyledRadioCardItem = BaseRadioCardItem + const selectedStyles = { + backgroundColor: selected ? lighten(theme.palette.primary.light, 0.7) : 'inherit', + } + const disabledStyles = { + opacity: 0.7, + backgroundColor: `${theme.palette.grey[300]} !important`, + pointerEvents: 'none', + } + + let styles = {} if (disabled) { - StyledRadioCardItem = DisabledRadioCardItem + styles = disabledStyles } else if (selected) { - StyledRadioCardItem = SelectedRadioCardItem + styles = selectedStyles } + return ( - + {icon} {control} @@ -97,7 +100,10 @@ function RadioCardGroup({ options, name, columns }: RadioCardGroupProps) { control={ } label={option.label} From 0bdb68fe8c9bc463a56ea0ea12e4cd46bb27d909 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Mon, 16 Jan 2023 21:56:17 +0100 Subject: [PATCH 37/78] a11y - add accessibility :focus styling to all buttons --- .../donation-flow/common/RadioCardGroup.tsx | 8 +++----- src/pages/_app.tsx | 2 +- src/stores/AuthContext.tsx | 11 ----------- src/styles/global.scss | 7 +++++++ 4 files changed, 11 insertions(+), 17 deletions(-) delete mode 100644 src/stores/AuthContext.tsx diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index 688548487..7d1e94eeb 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { useField } from 'formik' import { Card, CardProps, @@ -12,7 +13,6 @@ import { } from '@mui/material' import { styled, lighten } from '@mui/material/styles' import theme from 'common/theme' -import { useField } from 'formik' export const StyledRadioCardItem = styled(Card)(() => ({ padding: theme.spacing(2), @@ -20,10 +20,8 @@ export const StyledRadioCardItem = styled(Card)(() => ({ cursor: 'pointer', border: `1px solid ${theme.borders.dark}`, width: '100%', - // add black outline to selected card - '&:focus': { - outline: 'none', - border: `1px solid ${theme.palette.primary.main}`, + '&:focus-within': { + outline: `2px solid ${theme.palette.common.black}`, }, })) diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 5be9a4ac1..295f03cf3 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -96,7 +96,7 @@ function CustomApp({ {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} - + diff --git a/src/stores/AuthContext.tsx b/src/stores/AuthContext.tsx deleted file mode 100644 index 56d9ce5fe..000000000 --- a/src/stores/AuthContext.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client' - -import { SessionProvider, SessionProviderProps } from 'next-auth/react' -import { PropsWithChildren } from 'react' - -export default function AuthContext({ - children, - ...props -}: PropsWithChildren) { - return {children} -} diff --git a/src/styles/global.scss b/src/styles/global.scss index bb69f18ab..92bde9ae3 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -52,3 +52,10 @@ ul { } } } + +// Add outline to all MuiFocusVisible elements +// https://github.com/mui/material-ui/issues/35843 +/* stylelint-disable selector-class-pattern */ +.Mui-focusVisible { + outline: 2px solid $theme-primary-dark; +} From 9fd945691b0ed08e80112e82d1bcb1596ae159f0 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Mon, 16 Jan 2023 22:00:05 +0100 Subject: [PATCH 38/78] a11y - fix keyboard navigation for `LinkButton` component --- src/components/common/LinkButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/common/LinkButton.tsx b/src/components/common/LinkButton.tsx index 5d68faf80..0b1319060 100644 --- a/src/components/common/LinkButton.tsx +++ b/src/components/common/LinkButton.tsx @@ -16,7 +16,7 @@ const LinkButton = ( locale={locale} passHref style={{ pointerEvents: disabled ? 'none' : 'all' }}> - + + + ) +} + +export default InlineLoginForm From f8ea8dfddfa8d52014fb143ebcd94f5fa08f2a07 Mon Sep 17 00:00:00 2001 From: Dimitar Nizamov Date: Wed, 18 Jan 2023 23:35:06 +0100 Subject: [PATCH 42/78] 1292 - add translation to the `Authentication` --- src/components/donation-flow/steps/Authentication.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/donation-flow/steps/Authentication.tsx b/src/components/donation-flow/steps/Authentication.tsx index f1f86dc43..f1ad1542d 100644 --- a/src/components/donation-flow/steps/Authentication.tsx +++ b/src/components/donation-flow/steps/Authentication.tsx @@ -1,4 +1,5 @@ import { useContext } from 'react' +import { useTranslation } from 'react-i18next' import { useSession } from 'next-auth/react' import { Box } from '@mui/material' @@ -9,6 +10,7 @@ import InlineLoginForm from './InlineLoginForm' export default function Authentication() { const DonationContext = useContext(DonationFlowContext) const { data: session } = useSession() + const { t } = useTranslation('one-time-donation') const options = [ { value: 'login', @@ -25,7 +27,7 @@ export default function Authentication() { { value: 'anonymous', label: 'Anonymous', - content: <>Anonymous , + content:

{t('anonymous-menu.checkbox-label')}

, }, ] return ( From ebe465da43b67cc0c25a3f3eab5e983489087361 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 20 Jan 2023 19:33:04 +0100 Subject: [PATCH 43/78] 1292 - fix Radio checks on the authentication form accordions --- src/components/donation-flow/common/RadioAccordionGroup.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 8bb851e1f..74fe38525 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -14,7 +14,7 @@ import { useField } from 'formik' import theme from 'common/theme' export const BaseRadioAccordionItem = styled(Box)(() => ({ - '&:first-child': { + '&:first-of-type': { borderBottom: `1px solid ${theme.borders.dark}`, borderTopLeftRadius: theme.borders.semiRound, borderTopRightRadius: theme.borders.semiRound, @@ -110,7 +110,7 @@ function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProp control={ } + control={} label={option.label} disabled={option.disabled} /> From bc8fb642f69ff971bc31d462451c4759c320276a Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Fri, 20 Jan 2023 20:24:50 +0100 Subject: [PATCH 44/78] =?UTF-8?q?1292=20-=20add=20=C6=92orm=20validation?= =?UTF-8?q?=20and=20initial=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../donation-flow/DonationFlowForm.tsx | 74 ++++++++++++++++--- .../common/RadioAccordionGroup.tsx | 2 +- .../donation-flow/common/RadioCardGroup.tsx | 2 +- 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 7b5f2c712..f02440bde 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -3,7 +3,6 @@ import * as yup from 'yup' import { Form, Formik } from 'formik' import { Alert, Hidden, Unstable_Grid2 as Grid2 } from '@mui/material' -import { FirstStep } from 'gql/donations' import { CardRegion } from 'gql/donations.enums' import SubmitButton from 'components/common/form/SubmitButton' @@ -11,37 +10,88 @@ import StepSplitter from './common/StepSplitter' import Amount from './steps/Amount' import PaymentMethod from './steps/PaymentMethod' import Authentication from './steps/Authentication' +import { useSession } from 'next-auth/react' -const initialValues = { - amount: '', - payment: '', - amountWithFees: 0, +export enum DonationFormDataAuthState { + LOGIN = 'login', + REGISTER = 'register', + ANONYMOUS = 'anonymous', + AUTHENTICATED = 'authenticated', +} + +export enum DonationFormDataPaymentOption { + CARD = 'card', + BANK = 'bank', +} +export type DonationFormDataV2 = { + authentication: DonationFormDataAuthState + payment?: DonationFormDataPaymentOption + email?: string + cardRegion?: CardRegion + cardIncludeFees?: boolean + amountWithFees?: number + otherAmount?: number + amount?: string +} + +const initialValues: DonationFormDataV2 = { + amount: undefined, + email: undefined, + payment: undefined, + amountWithFees: undefined, cardIncludeFees: false, cardRegion: CardRegion.EU, otherAmount: 0, + authentication: DonationFormDataAuthState.LOGIN, } - //TODO: Should be a SchemaOf the whole form -export const validationSchema: yup.SchemaOf = yup +export const validationSchema: yup.SchemaOf = yup .object() .defined() .shape({ - payment: yup.string().oneOf(['card', 'bank']), + payment: yup + .string() + .oneOf(Object.values(DonationFormDataPaymentOption)) + .required() as yup.SchemaOf, amount: yup.string().when('payment', { is: 'card', - // Here we should fetch the possible payments to put into the oneOf, but it's not that important - then: yup.string().required(), + then: () => yup.string().required(), + }), + amountWithFees: yup.number().when('payment', { + is: 'card', + then: () => + yup.number().min(1, 'one-time-donation:errors-fields.amount-with-fees').required(), }), otherAmount: yup.number().when('amount', { is: 'other', - then: yup.number().min(1, 'one-time-donation:errors-fields.other-amount').required(), + then: () => yup.number().min(1, 'one-time-donation:errors-fields.other-amount').required(), }), + cardIncludeFees: yup.boolean(), + cardRegion: yup + .string() + .oneOf(Object.values(CardRegion)) + .when('payment', { + is: 'card', + then: () => yup.string().oneOf(Object.values(CardRegion)).required(), + }) as yup.SchemaOf, + authentication: yup + .string() + .oneOf(Object.values(DonationFormDataAuthState)) + .required() as yup.SchemaOf, + email: yup + .string() + .required() + .when('authentication', { + is: 'anonymous', + then: () => yup.string().email('one-time-donation:errors-fields.email').required(), + }), }) export function DonationFlowForm() { + const { data: session } = useSession() return ( { console.log(values) diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 74fe38525..7222ead0d 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -110,7 +110,7 @@ function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProp control={ } + control={} label={option.label} disabled={option.disabled} /> diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index 7d1e94eeb..8865b98b0 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -99,7 +99,7 @@ function RadioCardGroup({ options, name, columns }: RadioCardGroupProps) { From 1123c6c71e54705c02f81fae299c9e9a73069bc0 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 19:17:03 +0100 Subject: [PATCH 45/78] 1292 - add inline register form --- .../donation-flow/DonationFlowForm.tsx | 44 +++++--- .../common/RadioAccordionGroup.tsx | 3 +- .../donation-flow/steps/Authentication.tsx | 9 +- .../donation-flow/steps/InlineLoginForm.tsx | 13 ++- .../steps/InlineRegisterForm.tsx | 106 ++++++++++++++++++ 5 files changed, 150 insertions(+), 25 deletions(-) create mode 100644 src/components/donation-flow/steps/InlineRegisterForm.tsx diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index f02440bde..49e0d9440 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -35,16 +35,16 @@ export type DonationFormDataV2 = { } const initialValues: DonationFormDataV2 = { - amount: undefined, - email: undefined, - payment: undefined, - amountWithFees: undefined, + amount: '', + email: '', + payment: DonationFormDataPaymentOption.CARD, + amountWithFees: 0, cardIncludeFees: false, cardRegion: CardRegion.EU, otherAmount: 0, - authentication: DonationFormDataAuthState.LOGIN, + authentication: DonationFormDataAuthState.ANONYMOUS, } -//TODO: Should be a SchemaOf the whole form + export const validationSchema: yup.SchemaOf = yup .object() .defined() @@ -91,14 +91,20 @@ export function DonationFlowForm() { const { data: session } = useSession() return ( { console.log(values) }} validateOnMount validateOnBlur> - {({ handleSubmit }) => ( + {({ handleSubmit, values }) => (
- + - + - + Submit
- - TODO: Alerts row here + + + TODO: Alerts row here +
diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 7222ead0d..dd0a1120e 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -76,6 +76,7 @@ type Option = { content: React.ReactNode icon?: React.ReactNode disabled?: boolean + control?: React.ReactElement } export interface RadioAccordionGroupProps extends RadioGroupProps { @@ -110,7 +111,7 @@ function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProp control={ } + control={option.control || } label={option.label} disabled={option.disabled} /> diff --git a/src/components/donation-flow/steps/Authentication.tsx b/src/components/donation-flow/steps/Authentication.tsx index f1ad1542d..083a45133 100644 --- a/src/components/donation-flow/steps/Authentication.tsx +++ b/src/components/donation-flow/steps/Authentication.tsx @@ -1,14 +1,12 @@ -import { useContext } from 'react' import { useTranslation } from 'react-i18next' import { useSession } from 'next-auth/react' -import { Box } from '@mui/material' +import { Box, Checkbox, Radio } from '@mui/material' import RadioAccordionGroup from '../common/RadioAccordionGroup' -import { DonationFlowContext } from '../DonationFlowContext' import InlineLoginForm from './InlineLoginForm' +import InlineRegisterForm from './InlineRegisterForm' export default function Authentication() { - const DonationContext = useContext(DonationFlowContext) const { data: session } = useSession() const { t } = useTranslation('one-time-donation') const options = [ @@ -22,12 +20,13 @@ export default function Authentication() { value: 'register', label: 'Register', disabled: Boolean(session?.user), - content: <>Register Form, + content: , }, { value: 'anonymous', label: 'Anonymous', content:

{t('anonymous-menu.checkbox-label')}

, + control: session?.user ? : , }, ] return ( diff --git a/src/components/donation-flow/steps/InlineLoginForm.tsx b/src/components/donation-flow/steps/InlineLoginForm.tsx index 6aa4d3c86..9a56c51a4 100644 --- a/src/components/donation-flow/steps/InlineLoginForm.tsx +++ b/src/components/donation-flow/steps/InlineLoginForm.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useContext, useState } from 'react' import { useTranslation } from 'next-i18next' import { useFormikContext } from 'formik' import { Box, Button, CircularProgress, Grid, Typography } from '@mui/material' @@ -9,15 +9,16 @@ import { signIn } from 'next-auth/react' import { AlertStore } from 'stores/AlertStore' import PasswordField from 'components/common/form/PasswordField' import EmailField from 'components/common/form/EmailField' - -const onGoogleLogin = () => { - const resp = signIn('google') -} +import { DonationFlowContext } from '../DonationFlowContext' function InlineLoginForm() { const { t } = useTranslation('one-time-donation') const [loading, setLoading] = useState(false) const formik = useFormikContext() + const { campaign } = useContext(DonationFlowContext) + const onGoogleLogin = () => { + signIn('google', { callbackUrl: `campaigns/donation-v2/${campaign?.slug}` }) + } const onClick = async () => { try { @@ -42,7 +43,7 @@ function InlineLoginForm() { } } return ( - + diff --git a/src/components/donation-flow/steps/InlineRegisterForm.tsx b/src/components/donation-flow/steps/InlineRegisterForm.tsx new file mode 100644 index 000000000..922237b11 --- /dev/null +++ b/src/components/donation-flow/steps/InlineRegisterForm.tsx @@ -0,0 +1,106 @@ +import { Button, CircularProgress, Grid, Typography } from '@mui/material' +import React, { useState } from 'react' +import { signIn } from 'next-auth/react' +import { useTranslation } from 'next-i18next' +import theme from 'common/theme' +import { useRegister } from 'service/auth' +import { AlertStore } from 'stores/AlertStore' +import FormTextField from 'components/common/form/FormTextField' +import PasswordField from 'components/common/form/PasswordField' +import EmailField from 'components/common/form/EmailField' +import { useFormikContext } from 'formik' +import { OneTimeDonation } from 'gql/donations' +import { RegisterFormData } from 'components/auth/register/RegisterForm' +import { DonationFormDataAuthState } from '../DonationFlowForm' + +export default function InlineRegisterForm() { + const { t } = useTranslation() + const [loading, setLoading] = useState(false) + const { mutateAsync: register } = useRegister() + const formik = useFormikContext() + + const values: RegisterFormData = { + firstName: formik.values.registerFirstName as string, + lastName: formik.values.registerLastName as string, + email: formik.values.registerEmail as string, + password: formik.values.registerPassword as string, + confirmPassword: formik.values.confirmPassword as string, + terms: formik.values.terms as boolean, + gdpr: formik.values.gdpr as boolean, + } + + const onClick = async () => { + try { + setLoading(true) + + // Register in Keycloak + await register(values) + + // Authenticate + const resp = await signIn<'credentials'>('credentials', { + email: values.email, + password: values.password, + redirect: false, + }) + if (resp?.error) { + throw new Error(resp.error) + } + if (resp?.ok) { + setLoading(false) + AlertStore.show(t('auth:alerts.welcome'), 'success') + formik.setFieldValue('authentication', DonationFormDataAuthState.AUTHENTICATED) + } + } catch (error) { + console.error(error) + setLoading(false) + AlertStore.show(t('auth:alerts.invalid-login'), 'error') + } + } + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ) +} From cb0f24f1b08bad849a65260bfa9ef050a4f57707 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 19:20:15 +0100 Subject: [PATCH 46/78] 1292 - add authentication folder --- .env.local.example | 2 +- .../donation-flow/DonationFlowForm.tsx | 2 +- .../{ => authentication}/Authentication.tsx | 2 +- .../{ => authentication}/InlineLoginForm.tsx | 11 ++++---- .../InlineRegisterForm.tsx | 9 ++++--- yarn.lock | 26 +++++++++---------- 6 files changed, 27 insertions(+), 25 deletions(-) rename src/components/donation-flow/steps/{ => authentication}/Authentication.tsx (93%) rename src/components/donation-flow/steps/{ => authentication}/InlineLoginForm.tsx (94%) rename src/components/donation-flow/steps/{ => authentication}/InlineRegisterForm.tsx (96%) diff --git a/.env.local.example b/.env.local.example index 8ec1a117f..bd64f394c 100644 --- a/.env.local.example +++ b/.env.local.example @@ -32,7 +32,7 @@ GOOGLE_SECRET= ## Stripe ## ############## -STRIPE_PUBLIC_KEY= +STRIPE_PUBLIC_KEY=pk_test_51HmiW8JLlnbRmnT5ZYwSnviwwLOYo1Y4JdEVmkpzZ6HRQ8f3850Zkn9yLBowIeKytxLu9S24KS2oUU1U5OKPonkB00aGnDOyvK ## Paypal ## ############## diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 49e0d9440..4225968dc 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -9,7 +9,7 @@ import SubmitButton from 'components/common/form/SubmitButton' import StepSplitter from './common/StepSplitter' import Amount from './steps/Amount' import PaymentMethod from './steps/PaymentMethod' -import Authentication from './steps/Authentication' +import Authentication from './steps/authentication/Authentication' import { useSession } from 'next-auth/react' export enum DonationFormDataAuthState { diff --git a/src/components/donation-flow/steps/Authentication.tsx b/src/components/donation-flow/steps/authentication/Authentication.tsx similarity index 93% rename from src/components/donation-flow/steps/Authentication.tsx rename to src/components/donation-flow/steps/authentication/Authentication.tsx index 083a45133..1daef1d49 100644 --- a/src/components/donation-flow/steps/Authentication.tsx +++ b/src/components/donation-flow/steps/authentication/Authentication.tsx @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next' import { useSession } from 'next-auth/react' import { Box, Checkbox, Radio } from '@mui/material' -import RadioAccordionGroup from '../common/RadioAccordionGroup' +import RadioAccordionGroup from '../../common/RadioAccordionGroup' import InlineLoginForm from './InlineLoginForm' import InlineRegisterForm from './InlineRegisterForm' diff --git a/src/components/donation-flow/steps/InlineLoginForm.tsx b/src/components/donation-flow/steps/authentication/InlineLoginForm.tsx similarity index 94% rename from src/components/donation-flow/steps/InlineLoginForm.tsx rename to src/components/donation-flow/steps/authentication/InlineLoginForm.tsx index 9a56c51a4..56e503eb0 100644 --- a/src/components/donation-flow/steps/InlineLoginForm.tsx +++ b/src/components/donation-flow/steps/authentication/InlineLoginForm.tsx @@ -1,15 +1,16 @@ import React, { useContext, useState } from 'react' import { useTranslation } from 'next-i18next' +import { signIn } from 'next-auth/react' import { useFormikContext } from 'formik' -import { Box, Button, CircularProgress, Grid, Typography } from '@mui/material' +import { Box, Button, CircularProgress, Grid } from '@mui/material' + import theme from 'common/theme' import Google from 'common/icons/Google' -import { OneTimeDonation } from 'gql/donations' -import { signIn } from 'next-auth/react' -import { AlertStore } from 'stores/AlertStore' import PasswordField from 'components/common/form/PasswordField' import EmailField from 'components/common/form/EmailField' -import { DonationFlowContext } from '../DonationFlowContext' +import { OneTimeDonation } from 'gql/donations' +import { AlertStore } from 'stores/AlertStore' +import { DonationFlowContext } from '../../DonationFlowContext' function InlineLoginForm() { const { t } = useTranslation('one-time-donation') diff --git a/src/components/donation-flow/steps/InlineRegisterForm.tsx b/src/components/donation-flow/steps/authentication/InlineRegisterForm.tsx similarity index 96% rename from src/components/donation-flow/steps/InlineRegisterForm.tsx rename to src/components/donation-flow/steps/authentication/InlineRegisterForm.tsx index 922237b11..91b2a9962 100644 --- a/src/components/donation-flow/steps/InlineRegisterForm.tsx +++ b/src/components/donation-flow/steps/authentication/InlineRegisterForm.tsx @@ -1,17 +1,18 @@ -import { Button, CircularProgress, Grid, Typography } from '@mui/material' import React, { useState } from 'react' +import { Button, CircularProgress, Grid } from '@mui/material' +import { useFormikContext } from 'formik' import { signIn } from 'next-auth/react' import { useTranslation } from 'next-i18next' + import theme from 'common/theme' import { useRegister } from 'service/auth' import { AlertStore } from 'stores/AlertStore' import FormTextField from 'components/common/form/FormTextField' import PasswordField from 'components/common/form/PasswordField' import EmailField from 'components/common/form/EmailField' -import { useFormikContext } from 'formik' -import { OneTimeDonation } from 'gql/donations' import { RegisterFormData } from 'components/auth/register/RegisterForm' -import { DonationFormDataAuthState } from '../DonationFlowForm' +import { OneTimeDonation } from 'gql/donations' +import { DonationFormDataAuthState } from '../../DonationFlowForm' export default function InlineRegisterForm() { const { t } = useTranslation() diff --git a/yarn.lock b/yarn.lock index 2deb156c2..9b6e0f7ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -572,7 +572,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.16.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.16.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.18.9 resolution: "@babel/runtime@npm:7.18.9" dependencies: @@ -581,6 +581,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/runtime@npm:7.20.7" + dependencies: + regenerator-runtime: ^0.13.11 + checksum: 4629ce5c46f06cca9cfb9b7fc00d48003335a809888e2b91ec2069a2dcfbfef738480cff32ba81e0b7c290f8918e5c22ddcf2b710001464ee84ba62c7e32a3a3 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.18.9, @babel/runtime@npm:^7.20.1": version: 7.20.1 resolution: "@babel/runtime@npm:7.20.1" @@ -590,15 +599,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/runtime@npm:7.20.7" - dependencies: - regenerator-runtime: ^0.13.11 - checksum: 4629ce5c46f06cca9cfb9b7fc00d48003335a809888e2b91ec2069a2dcfbfef738480cff32ba81e0b7c290f8918e5c22ddcf2b710001464ee84ba62c7e32a3a3 - languageName: node - linkType: hard - "@babel/template@npm:^7.16.0, @babel/template@npm:^7.3.3": version: 7.16.0 resolution: "@babel/template@npm:7.16.0" @@ -2417,9 +2417,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.165": - version: 4.14.170 - resolution: "@types/lodash@npm:4.14.170" - checksum: 238a440804e787b85461cc280a11926c80779f7502fec21a70b4424d5feba4cc34cdcbbbc1dca2ec5e75b06d5dc42e42add798be3b6651e8dce9f4b5318d6022 + version: 4.14.191 + resolution: "@types/lodash@npm:4.14.191" + checksum: ba0d5434e10690869f32d5ea49095250157cae502f10d57de0a723fd72229ce6c6a4979576f0f13e0aa9fbe3ce2457bfb9fa7d4ec3d6daba56730a51906d1491 languageName: node linkType: hard From 8204fa85aaa10c3e2ff393d3befaa91866d62aba Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 19:28:33 +0100 Subject: [PATCH 47/78] 1292 - style form donate button --- .../donation-flow/DonationFlowForm.tsx | 39 ++++++++++--------- .../steps/authentication/Authentication.tsx | 4 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 4225968dc..6c287b521 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -1,7 +1,7 @@ import React from 'react' import * as yup from 'yup' import { Form, Formik } from 'formik' -import { Alert, Hidden, Unstable_Grid2 as Grid2 } from '@mui/material' +import { Alert, Box, Hidden, Unstable_Grid2 as Grid2 } from '@mui/material' import { CardRegion } from 'gql/donations.enums' import SubmitButton from 'components/common/form/SubmitButton' @@ -115,24 +115,25 @@ export function DonationFlowForm() { marginRight: 'auto', }} autoComplete="off"> - - - - - - - - Submit + + + + + + + + +
diff --git a/src/components/donation-flow/steps/authentication/Authentication.tsx b/src/components/donation-flow/steps/authentication/Authentication.tsx index 1daef1d49..8a2fbeb2c 100644 --- a/src/components/donation-flow/steps/authentication/Authentication.tsx +++ b/src/components/donation-flow/steps/authentication/Authentication.tsx @@ -24,9 +24,9 @@ export default function Authentication() { }, { value: 'anonymous', - label: 'Anonymous', - content:

{t('anonymous-menu.checkbox-label')}

, + label: 'Without registration', control: session?.user ? : , + content: <>, }, ] return ( From fe2d11b5d4d6ca0c9cfa787d6f8c8e2ca127e3f0 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 19:36:55 +0100 Subject: [PATCH 48/78] 1292 - return yup.then to non-functions --- src/components/donation-flow/DonationFlowForm.tsx | 8 ++++---- src/components/donation-flow/steps/Amount.tsx | 2 +- .../steps/authentication/Authentication.tsx | 11 +++++++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index 6c287b521..c8d3fe98d 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -55,7 +55,7 @@ export const validationSchema: yup.SchemaOf = yup .required() as yup.SchemaOf, amount: yup.string().when('payment', { is: 'card', - then: () => yup.string().required(), + then: yup.string().required(), }), amountWithFees: yup.number().when('payment', { is: 'card', @@ -64,7 +64,7 @@ export const validationSchema: yup.SchemaOf = yup }), otherAmount: yup.number().when('amount', { is: 'other', - then: () => yup.number().min(1, 'one-time-donation:errors-fields.other-amount').required(), + then: yup.number().min(1, 'one-time-donation:errors-fields.other-amount').required(), }), cardIncludeFees: yup.boolean(), cardRegion: yup @@ -72,7 +72,7 @@ export const validationSchema: yup.SchemaOf = yup .oneOf(Object.values(CardRegion)) .when('payment', { is: 'card', - then: () => yup.string().oneOf(Object.values(CardRegion)).required(), + then: yup.string().oneOf(Object.values(CardRegion)).required(), }) as yup.SchemaOf, authentication: yup .string() @@ -83,7 +83,7 @@ export const validationSchema: yup.SchemaOf = yup .required() .when('authentication', { is: 'anonymous', - then: () => yup.string().email('one-time-donation:errors-fields.email').required(), + then: yup.string().email('one-time-donation:errors-fields.email').required(), }), }) diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index f4af717b5..60a6d362b 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -97,7 +97,7 @@ export default function Amount() { .concat({ label: t('first-step.other'), value: 'other' }) || [] } /> - + { + if (session?.user) { + formik.setFieldValue('authentication', undefined) + } + }, [session?.user]) const options = [ { value: 'login', @@ -29,6 +35,7 @@ export default function Authentication() { content: <>, }, ] + return ( From a5d9637a890d28a0ad5b69c36bff0126addaac81 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 19:54:26 +0100 Subject: [PATCH 49/78] 1292 - fix payment method grid problem --- src/components/donation-flow/common/RadioCardGroup.tsx | 10 +++++----- src/components/donation-flow/steps/PaymentMethod.tsx | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index 8865b98b0..057fff1bb 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -9,7 +9,7 @@ import { RadioGroup, RadioGroupProps, Stack, - Unstable_Grid2 as Grid2, + Grid, } from '@mui/material' import { styled, lighten } from '@mui/material/styles' import theme from 'common/theme' @@ -84,9 +84,9 @@ function RadioCardGroup({ options, name, columns }: RadioCardGroupProps) { component="fieldset" error={Boolean(meta.error) && Boolean(meta.touched)}> - + {options.map((option) => ( - + setValue(option.value)} control={ @@ -111,9 +111,9 @@ function RadioCardGroup({ options, name, columns }: RadioCardGroupProps) { selected={field.value === option.value && !option.disabled} disabled={option.disabled} /> - + ))} - +
) diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx index 68245ecdf..c5870fa90 100644 --- a/src/components/donation-flow/steps/PaymentMethod.tsx +++ b/src/components/donation-flow/steps/PaymentMethod.tsx @@ -110,7 +110,7 @@ export default function PaymentMethod() { }, ] return ( - + <> {isSmall ? ( ) : ( @@ -127,6 +127,6 @@ export default function PaymentMethod() { ) : null} )} - + ) } From 3d955a9006def3069da8a2ce2a4124a1469c408b Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 19:57:26 +0100 Subject: [PATCH 50/78] 1292 - remove stripe public key from example env --- .env.local.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.local.example b/.env.local.example index bd64f394c..8ec1a117f 100644 --- a/.env.local.example +++ b/.env.local.example @@ -32,7 +32,7 @@ GOOGLE_SECRET= ## Stripe ## ############## -STRIPE_PUBLIC_KEY=pk_test_51HmiW8JLlnbRmnT5ZYwSnviwwLOYo1Y4JdEVmkpzZ6HRQ8f3850Zkn9yLBowIeKytxLu9S24KS2oUU1U5OKPonkB00aGnDOyvK +STRIPE_PUBLIC_KEY= ## Paypal ## ############## From b01263aff851474610f10c2bf3fc80466ec3cd3d Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 20:31:19 +0100 Subject: [PATCH 51/78] 1292 - add heading to the form --- src/components/donation-flow/steps/Amount.tsx | 4 ++-- src/components/donation-flow/steps/PaymentMethod.tsx | 5 ++++- .../donation-flow/steps/authentication/Authentication.tsx | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 60a6d362b..5206e25d9 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -81,10 +81,10 @@ export default function Amount() { return ( - + {t('first-step.amount')} - + + + Как желаете да дарите? + {isSmall ? ( ) : ( diff --git a/src/components/donation-flow/steps/authentication/Authentication.tsx b/src/components/donation-flow/steps/authentication/Authentication.tsx index 9533f3293..1028318e3 100644 --- a/src/components/donation-flow/steps/authentication/Authentication.tsx +++ b/src/components/donation-flow/steps/authentication/Authentication.tsx @@ -1,5 +1,5 @@ import { useSession } from 'next-auth/react' -import { Box, Checkbox, Radio } from '@mui/material' +import { Box, Checkbox, Radio, Typography } from '@mui/material' import RadioAccordionGroup from '../../common/RadioAccordionGroup' import InlineLoginForm from './InlineLoginForm' @@ -38,6 +38,9 @@ export default function Authentication() { return ( + + Как предпочитате да продължите? + ) From 289a996e2884ac7296e0402b1e2a3d9667806171 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 20:33:18 +0100 Subject: [PATCH 52/78] 1292 - move payment method components to a folder --- .../donation-flow/DonationFlowForm.tsx | 2 +- .../donation-flow/steps/PaymentMethod.tsx | 135 ------------------ .../steps/payment-method/PaymentMethod.tsx | 80 +++++++++++ .../steps/payment-method/TaxesCheckbox.tsx | 60 ++++++++ 4 files changed, 141 insertions(+), 136 deletions(-) delete mode 100644 src/components/donation-flow/steps/PaymentMethod.tsx create mode 100644 src/components/donation-flow/steps/payment-method/PaymentMethod.tsx create mode 100644 src/components/donation-flow/steps/payment-method/TaxesCheckbox.tsx diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index c8d3fe98d..ab994af5a 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -8,7 +8,7 @@ import SubmitButton from 'components/common/form/SubmitButton' import StepSplitter from './common/StepSplitter' import Amount from './steps/Amount' -import PaymentMethod from './steps/PaymentMethod' +import PaymentMethod from './steps/payment-method/PaymentMethod' import Authentication from './steps/authentication/Authentication' import { useSession } from 'next-auth/react' diff --git a/src/components/donation-flow/steps/PaymentMethod.tsx b/src/components/donation-flow/steps/PaymentMethod.tsx deleted file mode 100644 index 699091fe9..000000000 --- a/src/components/donation-flow/steps/PaymentMethod.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useContext } from 'react' -import { Trans, useTranslation } from 'react-i18next' -import { Typography, Unstable_Grid2 as Grid2, useMediaQuery } from '@mui/material' -import { useField, useFormikContext } from 'formik' - -import { CardRegion } from 'gql/donations.enums' -import { OneTimeDonation } from 'gql/donations' -import theme from 'common/theme' -import { moneyPublicDecimals2 } from 'common/util/money' -import CheckboxField from 'components/common/form/CheckboxField' -import FormSelectField from 'components/common/form/FormSelectField' - -import RadioCardGroup from '../common/RadioCardGroup' -import RadioAccordionGroup from '../common/RadioAccordionGroup' -import CardIcon from '../icons/CardIcon' -import BankIcon from '../icons/BankIcon' -import PaymentDetailsStripeForm from '../stripe/PaymentDetailsStripeForm' -import { DonationFlowContext } from '../DonationFlowContext' - -const TaxesCheckbox = () => { - const { t } = useTranslation('one-time-donation') - const [amountWithFees] = useField('amountWithFees') - const [amountWithoutFees] = useField('amountWithoutFees') - return ( - <> - - - {t('third-step.card-include-fees')}} - /> - - - - - - - - ) -} - -export default function PaymentMethod() { - const formik = useFormikContext() - const DonationContext = useContext(DonationFlowContext) - const isSmall = useMediaQuery(theme.breakpoints.down('md')) - const [payment] = useField('payment') - const options = [ - { - value: 'card', - label: 'Card', - icon: , - disabled: !formik.values.amount, - }, - { - value: 'bank', - label: 'Bank Transfer', - icon: , - }, - ] - const mobileOptions = [ - { - value: 'card', - label: 'Card', - icon: , - disabled: !formik.values.amount, - content: ( - <> - {DonationContext.stripePaymentIntent ? ( - - ) : null} - - ), - }, - { - value: 'bank', - label: 'Bank Transfer', - icon: , - content: <>TODO: Add Bank Transfer Content, - }, - ] - return ( - <> - - Как желаете да дарите? - - {isSmall ? ( - - ) : ( - <> - - {payment.value === 'card' && DonationContext.stripePaymentIntent ? ( - <> - - - - ) : null} - - )} - - ) -} diff --git a/src/components/donation-flow/steps/payment-method/PaymentMethod.tsx b/src/components/donation-flow/steps/payment-method/PaymentMethod.tsx new file mode 100644 index 000000000..6ae0d5006 --- /dev/null +++ b/src/components/donation-flow/steps/payment-method/PaymentMethod.tsx @@ -0,0 +1,80 @@ +import React, { useContext } from 'react' +import { Typography, useMediaQuery } from '@mui/material' +import { useField, useFormikContext } from 'formik' + +import { OneTimeDonation } from 'gql/donations' +import theme from 'common/theme' + +import RadioCardGroup from '../../common/RadioCardGroup' +import RadioAccordionGroup from '../../common/RadioAccordionGroup' +import CardIcon from '../../icons/CardIcon' +import BankIcon from '../../icons/BankIcon' +import PaymentDetailsStripeForm from '../../stripe/PaymentDetailsStripeForm' +import { DonationFlowContext } from '../../DonationFlowContext' +import { TaxesCheckbox } from './TaxesCheckbox' + +export default function PaymentMethod() { + const formik = useFormikContext() + const DonationContext = useContext(DonationFlowContext) + const isSmall = useMediaQuery(theme.breakpoints.down('md')) + const [payment] = useField('payment') + const options = [ + { + value: 'card', + label: 'Card', + icon: , + disabled: !formik.values.amount, + }, + { + value: 'bank', + label: 'Bank Transfer', + icon: , + }, + ] + const mobileOptions = [ + { + value: 'card', + label: 'Card', + icon: , + disabled: !formik.values.amount, + content: ( + <> + {DonationContext.stripePaymentIntent ? ( + + ) : null} + + ), + }, + { + value: 'bank', + label: 'Bank Transfer', + icon: , + content: <>TODO: Add Bank Transfer Content, + }, + ] + return ( + <> + + Как желаете да дарите? + + {isSmall ? ( + + ) : ( + <> + + {payment.value === 'card' && DonationContext.stripePaymentIntent ? ( + <> + + + + ) : null} + + )} + + ) +} diff --git a/src/components/donation-flow/steps/payment-method/TaxesCheckbox.tsx b/src/components/donation-flow/steps/payment-method/TaxesCheckbox.tsx new file mode 100644 index 000000000..ff1479045 --- /dev/null +++ b/src/components/donation-flow/steps/payment-method/TaxesCheckbox.tsx @@ -0,0 +1,60 @@ +import React from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { Typography, Unstable_Grid2 as Grid2 } from '@mui/material' +import { useField } from 'formik' + +import { CardRegion } from 'gql/donations.enums' +import { moneyPublicDecimals2 } from 'common/util/money' +import CheckboxField from 'components/common/form/CheckboxField' +import FormSelectField from 'components/common/form/FormSelectField' + +export const TaxesCheckbox = () => { + const { t } = useTranslation('one-time-donation') + const [amountWithFees] = useField('amountWithFees') + const [amountWithoutFees] = useField('amountWithoutFees') + return ( + <> + + + {t('third-step.card-include-fees')}} + /> + + + + + + + + ) +} From 33a91df7741e6b180bd39fd88e7991ef4b2c4c37 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 20:34:10 +0100 Subject: [PATCH 53/78] 1292 - change structure of the donation-flow directory --- .../donation-flow/{stripe => helpers}/stripe-fee-calculator.ts | 0 src/components/donation-flow/steps/Amount.tsx | 2 +- .../payment-method}/PaymentDetailsStripeForm.tsx | 0 .../donation-flow/steps/payment-method/PaymentMethod.tsx | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename src/components/donation-flow/{stripe => helpers}/stripe-fee-calculator.ts (100%) rename src/components/donation-flow/{stripe => steps/payment-method}/PaymentDetailsStripeForm.tsx (100%) diff --git a/src/components/donation-flow/stripe/stripe-fee-calculator.ts b/src/components/donation-flow/helpers/stripe-fee-calculator.ts similarity index 100% rename from src/components/donation-flow/stripe/stripe-fee-calculator.ts rename to src/components/donation-flow/helpers/stripe-fee-calculator.ts diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 5206e25d9..87d01cbeb 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -15,7 +15,7 @@ import FormTextField from 'components/common/form/FormTextField' import CheckboxField from 'components/common/form/CheckboxField' import { useCreatePaymentIntent } from 'service/donation' -import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../stripe/stripe-fee-calculator' +import { stripeFeeCalculator, stripeIncludeFeeCalculator } from '../helpers/stripe-fee-calculator' import { DonationFlowContext } from '../DonationFlowContext' const PREFIX = 'AMOUNT' diff --git a/src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx similarity index 100% rename from src/components/donation-flow/stripe/PaymentDetailsStripeForm.tsx rename to src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx diff --git a/src/components/donation-flow/steps/payment-method/PaymentMethod.tsx b/src/components/donation-flow/steps/payment-method/PaymentMethod.tsx index 6ae0d5006..3ec9c4018 100644 --- a/src/components/donation-flow/steps/payment-method/PaymentMethod.tsx +++ b/src/components/donation-flow/steps/payment-method/PaymentMethod.tsx @@ -9,7 +9,7 @@ import RadioCardGroup from '../../common/RadioCardGroup' import RadioAccordionGroup from '../../common/RadioAccordionGroup' import CardIcon from '../../icons/CardIcon' import BankIcon from '../../icons/BankIcon' -import PaymentDetailsStripeForm from '../../stripe/PaymentDetailsStripeForm' +import PaymentDetailsStripeForm from './PaymentDetailsStripeForm' import { DonationFlowContext } from '../../DonationFlowContext' import { TaxesCheckbox } from './TaxesCheckbox' From 1a088ec27c80732281d4f8058dcc3dbaa0c2d858 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sat, 21 Jan 2023 20:57:13 +0100 Subject: [PATCH 54/78] 1292 - add docs to the RadioGroup components --- .../donation-flow/DonationFlowContext.tsx | 1 - .../donation-flow/DonationFlowPage.tsx | 1 - .../common/RadioAccordionGroup.tsx | 28 +++++++++++++++++++ .../donation-flow/common/RadioCardGroup.tsx | 19 +++++++++++++ src/components/donation-flow/steps/Amount.tsx | 2 +- .../PaymentDetailsStripeForm.tsx | 3 +- 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/components/donation-flow/DonationFlowContext.tsx b/src/components/donation-flow/DonationFlowContext.tsx index bcc5d9bc5..b8d041aff 100644 --- a/src/components/donation-flow/DonationFlowContext.tsx +++ b/src/components/donation-flow/DonationFlowContext.tsx @@ -14,7 +14,6 @@ type DonationContext = { export const DonationFlowContext = React.createContext({} as DonationContext) export const DonationFlowProvider = ({ children }: PropsWithChildren) => { - //get the campaign with react-query and pass it to the context const router = useRouter() const slug = String(router.query.slug) const { data, isLoading } = useViewCampaign(slug) diff --git a/src/components/donation-flow/DonationFlowPage.tsx b/src/components/donation-flow/DonationFlowPage.tsx index 68d22beb4..3e1d0682d 100644 --- a/src/components/donation-flow/DonationFlowPage.tsx +++ b/src/components/donation-flow/DonationFlowPage.tsx @@ -79,7 +79,6 @@ export default function DonationFlowPage({ slug }: { slug: string }) { ( opacity: 0.7, backgroundColor: `${theme.palette.grey[300]} !important`, pointerEvents: 'none', + borderColor: `${theme.palette.grey[500]} !important`, })) interface RadioAccordionItemProps extends BoxProps { @@ -80,10 +81,37 @@ type Option = { } export interface RadioAccordionGroupProps extends RadioGroupProps { + /** + * The options to display in the radio group. + */ options: Option[] + + /** + * The name of the field. + * This is used to link the radio group to the form. + */ name: string } +/** + * A radio group that displays a list of options. Each option can be expanded to show more content. + * @example + * , + * }, + * { + * value: 'register', + * label: 'Register', + * disabled: Boolean(session?.user), + * content: , + * }] /> + */ function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProps) { const [field, meta, { setValue }] = useField(name) const handleChange = (event: React.ChangeEvent) => { diff --git a/src/components/donation-flow/common/RadioCardGroup.tsx b/src/components/donation-flow/common/RadioCardGroup.tsx index 057fff1bb..23695939d 100644 --- a/src/components/donation-flow/common/RadioCardGroup.tsx +++ b/src/components/donation-flow/common/RadioCardGroup.tsx @@ -40,6 +40,7 @@ function RadioCardItem({ control, icon, selected, disabled, ...rest }: StyledRad opacity: 0.7, backgroundColor: `${theme.palette.grey[300]} !important`, pointerEvents: 'none', + borderColor: `${theme.palette.grey[500]} !important`, } let styles = {} @@ -72,6 +73,24 @@ export interface RadioCardGroupProps extends RadioGroupProps { columns: 1 | 2 | 3 | 4 | 6 | 12 } +/** + * RadioCardGroup is a group of radio buttons that display a card for each option. + * The element is hidden, but accessible to screen readers. + * @example + * , + * }, + * { + * value: '25', + * label: '$25', + * icon: , + * }, + */ function RadioCardGroup({ options, name, columns }: RadioCardGroupProps) { const [field, meta, { setValue }] = useField(name) const handleChange = (event: React.ChangeEvent) => { diff --git a/src/components/donation-flow/steps/Amount.tsx b/src/components/donation-flow/steps/Amount.tsx index 87d01cbeb..1724f424d 100644 --- a/src/components/donation-flow/steps/Amount.tsx +++ b/src/components/donation-flow/steps/Amount.tsx @@ -34,6 +34,7 @@ const Root = styled('div')(() => ({ export default function Amount() { const { data: prices } = useSinglePriceList() const DonationContext = useContext(DonationFlowContext) + const formik = useFormikContext() const { t } = useTranslation('one-time-donation') const mobile = useMediaQuery('(max-width:600px)') @@ -53,7 +54,6 @@ export default function Amount() { const intent = paymentIntentMutation.data?.data DonationContext.setStripePaymentIntent(intent || null) }, [paymentIntentMutation]) - const formik = useFormikContext() useEffect(() => { const chosenAmount = diff --git a/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx index 35da55599..a6142a834 100644 --- a/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx @@ -12,7 +12,7 @@ const appearance: Appearance = { variables: { colorPrimary: theme.palette.primary.main, colorBackground: theme.palette.background.paper, - // colorText: theme.palette.text.primary resolves to rgba(0, 0, 0, 0.87) and Stripe doesn't like that + // colorText: theme.palette.text.primary resolves to rgba(0, 0, 0, 0.87) and Stripe doesn't accept rgba values colorText: 'rgb(0, 0, 0)', colorDanger: theme.palette.error.main, fontFamily: "Montserrat, 'Helvetica Neue', Helvetica, Arial, sans-serif", @@ -46,7 +46,6 @@ export default function PaymentDetailsStripeForm({ containerProps, }: PaymentDetailsStripeFormProps) { const { i18n } = useTranslation() - // use session to get the email const { data: session } = useSession() return ( Date: Sun, 22 Jan 2023 17:43:09 +0100 Subject: [PATCH 55/78] 1292 - fix context import --- .../steps/payment-method/PaymentDetailsStripeForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx b/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx index ca4a133f7..7229b4bb5 100644 --- a/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx +++ b/src/components/donation-flow/steps/payment-method/PaymentDetailsStripeForm.tsx @@ -6,7 +6,7 @@ import { Elements, LinkAuthenticationElement, PaymentElement } from '@stripe/rea import { Box, BoxProps } from '@mui/material' import theme from 'common/theme' -import { DonationFlowContext } from '../DonationFlowContext' +import { DonationFlowContext } from '../../DonationFlowContext' const appearance: Appearance = { theme: 'stripe', From 8313b904a148406d8e1d99fb489089e4a640cad2 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sun, 22 Jan 2023 17:58:47 +0100 Subject: [PATCH 56/78] 1292 - remove `stripe-fee-calculators.ts` from the payment-method folder --- .../payment-method/stripe-fee-calculator.ts | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 src/components/donation-flow/steps/payment-method/stripe-fee-calculator.ts diff --git a/src/components/donation-flow/steps/payment-method/stripe-fee-calculator.ts b/src/components/donation-flow/steps/payment-method/stripe-fee-calculator.ts deleted file mode 100644 index ed71ff2f2..000000000 --- a/src/components/donation-flow/steps/payment-method/stripe-fee-calculator.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { CardRegion } from 'gql/donations.enums' - -/** - * Calculates total charge amount in stotinki so that donation of netAmount is received after deducting Stripe fees - * References: - * - applied fees https://stripe.com/en-bg/pricing - * - formula for including fees: https://support.stripe.com/questions/passing-the-stripe-fee-on-to-customers - * - testing with International cards: https://stripe.com/docs/testing#international-cards - * @param netAmount expected in stotinki - * @returns - */ -export function stripeIncludeFeeCalculator(netAmount: number, region: CardRegion) { - switch (region) { - case CardRegion.EU: { - return stripeIncludeFeeCalculatorEU(netAmount) - } - case CardRegion.UK: { - return stripeIncludeFeeCalculatorUK(netAmount) - } - case CardRegion.Other: { - return stripeIncludeFeeCalculatorOther(netAmount) - } - } -} - -export function stripeIncludeFeeCalculatorEU(netAmount: number) { - return (netAmount + 50) / (1 - 0.012) -} - -export function stripeIncludeFeeCalculatorUK(netAmount: number) { - return (netAmount + 50) / (1 - 0.025) -} - -export function stripeIncludeFeeCalculatorOther(netAmount: number) { - return (netAmount + 50) / (1 - 0.029) -} - -/** - * Calculates Stripe fees based on card region - * References: - * - applied fees https://stripe.com/en-bg/pricing - * - formula for including fees: https://support.stripe.com/questions/passing-the-stripe-fee-on-to-customers - * - testing with International cards: https://stripe.com/docs/testing#international-cards - * @param chargedAmount expected in stotinki - * @returns - */ -export function stripeFeeCalculator(chargedAmount: number, region: CardRegion) { - switch (region) { - case CardRegion.EU: { - return chargedAmount * 0.012 + 50 - } - case CardRegion.UK: { - return chargedAmount * 0.025 + 50 - } - case CardRegion.Other: { - return chargedAmount * 0.029 + 50 - } - } -} From 9b4d13dc3f373a094c8e2da23b025f764aed4d7c Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Sun, 22 Jan 2023 22:09:35 +0100 Subject: [PATCH 57/78] 1292 - add `anonymous` as a seperate checkbox and field --- .../donation-flow/DonationFlowForm.tsx | 26 +++++------ .../common/RadioAccordionGroup.tsx | 3 +- .../steps/authentication/Authentication.tsx | 45 ++++++++++++++----- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index ab994af5a..e1944691b 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -15,7 +15,6 @@ import { useSession } from 'next-auth/react' export enum DonationFormDataAuthState { LOGIN = 'login', REGISTER = 'register', - ANONYMOUS = 'anonymous', AUTHENTICATED = 'authenticated', } @@ -24,25 +23,27 @@ export enum DonationFormDataPaymentOption { BANK = 'bank', } export type DonationFormDataV2 = { - authentication: DonationFormDataAuthState - payment?: DonationFormDataPaymentOption - email?: string - cardRegion?: CardRegion - cardIncludeFees?: boolean + anonymous: boolean + authentication: DonationFormDataAuthState | null + payment: DonationFormDataPaymentOption | null + email: string + cardRegion: CardRegion + cardIncludeFees: boolean + amount?: string amountWithFees?: number otherAmount?: number - amount?: string } const initialValues: DonationFormDataV2 = { amount: '', email: '', - payment: DonationFormDataPaymentOption.CARD, + payment: null, amountWithFees: 0, cardIncludeFees: false, cardRegion: CardRegion.EU, otherAmount: 0, - authentication: DonationFormDataAuthState.ANONYMOUS, + authentication: null, + anonymous: false, } export const validationSchema: yup.SchemaOf = yup @@ -66,7 +67,7 @@ export const validationSchema: yup.SchemaOf = yup is: 'other', then: yup.number().min(1, 'one-time-donation:errors-fields.other-amount').required(), }), - cardIncludeFees: yup.boolean(), + cardIncludeFees: yup.boolean().required(), cardRegion: yup .string() .oneOf(Object.values(CardRegion)) @@ -78,6 +79,7 @@ export const validationSchema: yup.SchemaOf = yup .string() .oneOf(Object.values(DonationFormDataAuthState)) .required() as yup.SchemaOf, + anonymous: yup.boolean().required(), email: yup .string() .required() @@ -94,9 +96,7 @@ export function DonationFlowForm() { initialValues={{ ...initialValues, email: session?.user?.email ?? '', - authentication: session?.user - ? DonationFormDataAuthState.AUTHENTICATED - : DonationFormDataAuthState.ANONYMOUS, + authentication: session?.user ? DonationFormDataAuthState.AUTHENTICATED : null, }} validationSchema={validationSchema} onSubmit={async (values) => { diff --git a/src/components/donation-flow/common/RadioAccordionGroup.tsx b/src/components/donation-flow/common/RadioAccordionGroup.tsx index 35816c8bd..6bf844d44 100644 --- a/src/components/donation-flow/common/RadioAccordionGroup.tsx +++ b/src/components/donation-flow/common/RadioAccordionGroup.tsx @@ -113,7 +113,7 @@ export interface RadioAccordionGroupProps extends RadioGroupProps { * }] */ -function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProps) { +function RadioAccordionGroup({ options, name, sx, ...rest }: RadioAccordionGroupProps) { const [field, meta, { setValue }] = useField(name) const handleChange = (event: React.ChangeEvent) => { setValue(event.target.value) @@ -131,6 +131,7 @@ function RadioAccordionGroup({ options, name, ...rest }: RadioAccordionGroupProp sx={{ border: `1px solid ${theme.borders.dark}`, borderRadius: theme.borders.semiRound, + ...sx, }} {...rest}> {options.map((option) => ( diff --git a/src/components/donation-flow/steps/authentication/Authentication.tsx b/src/components/donation-flow/steps/authentication/Authentication.tsx index 1028318e3..7f3009c19 100644 --- a/src/components/donation-flow/steps/authentication/Authentication.tsx +++ b/src/components/donation-flow/steps/authentication/Authentication.tsx @@ -1,20 +1,34 @@ import { useSession } from 'next-auth/react' -import { Box, Checkbox, Radio, Typography } from '@mui/material' +import { Box, IconButton, Tooltip, Typography } from '@mui/material' import RadioAccordionGroup from '../../common/RadioAccordionGroup' import InlineLoginForm from './InlineLoginForm' import InlineRegisterForm from './InlineRegisterForm' import { useEffect } from 'react' import { useFormikContext } from 'formik' +import CheckboxField from 'components/common/form/CheckboxField' +import theme from 'common/theme' +import { Info } from '@mui/icons-material' +import { DonationFormDataV2 } from 'components/donation-flow/DonationFlowForm' export default function Authentication() { const { data: session } = useSession() - const formik = useFormikContext() + const { + values: { anonymous }, + setFieldValue, + } = useFormikContext() + useEffect(() => { if (session?.user) { - formik.setFieldValue('authentication', undefined) + setFieldValue('authentication', null) } }, [session?.user]) + + useEffect(() => { + if (anonymous) { + setFieldValue('authentication', null) + } + }, [anonymous]) const options = [ { value: 'login', @@ -28,12 +42,6 @@ export default function Authentication() { disabled: Boolean(session?.user), content: , }, - { - value: 'anonymous', - label: 'Without registration', - control: session?.user ? : , - content: <>, - }, ] return ( @@ -41,7 +49,24 @@ export default function Authentication() { Как предпочитате да продължите? - + + + I want to be anonymous + + + + + + + } + name="anonymous" + /> ) } From ea8c13d6bf3b568f732608827127954b06e44793 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Mon, 23 Jan 2023 21:25:12 +0100 Subject: [PATCH 58/78] feat-7134 - add a `NOREGISTER` state to the authentication and a conditionally rendered checkbox --- .../donation-flow/DonationFlowForm.tsx | 1 + .../steps/authentication/Authentication.tsx | 57 ++++++++++++++----- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/components/donation-flow/DonationFlowForm.tsx b/src/components/donation-flow/DonationFlowForm.tsx index e1944691b..a20d18274 100644 --- a/src/components/donation-flow/DonationFlowForm.tsx +++ b/src/components/donation-flow/DonationFlowForm.tsx @@ -16,6 +16,7 @@ export enum DonationFormDataAuthState { LOGIN = 'login', REGISTER = 'register', AUTHENTICATED = 'authenticated', + NOREGISTER = 'noregister', } export enum DonationFormDataPaymentOption { diff --git a/src/components/donation-flow/steps/authentication/Authentication.tsx b/src/components/donation-flow/steps/authentication/Authentication.tsx index 7f3009c19..be80a1db5 100644 --- a/src/components/donation-flow/steps/authentication/Authentication.tsx +++ b/src/components/donation-flow/steps/authentication/Authentication.tsx @@ -1,5 +1,5 @@ import { useSession } from 'next-auth/react' -import { Box, IconButton, Tooltip, Typography } from '@mui/material' +import { Box, Checkbox, FormControlLabel, IconButton, Tooltip, Typography } from '@mui/material' import RadioAccordionGroup from '../../common/RadioAccordionGroup' import InlineLoginForm from './InlineLoginForm' @@ -9,7 +9,10 @@ import { useFormikContext } from 'formik' import CheckboxField from 'components/common/form/CheckboxField' import theme from 'common/theme' import { Info } from '@mui/icons-material' -import { DonationFormDataV2 } from 'components/donation-flow/DonationFlowForm' +import { + DonationFormDataAuthState, + DonationFormDataV2, +} from 'components/donation-flow/DonationFlowForm' export default function Authentication() { const { data: session } = useSession() @@ -54,19 +57,43 @@ export default function Authentication() { name="authentication" options={options} /> - - I want to be anonymous - - - - - - - } - name="anonymous" - /> + {session?.user ? ( + + Искам да съм анонимен + + + + + + + } + name="anonymous" + /> + ) : ( + { + if (checked) { + setFieldValue('authentication', DonationFormDataAuthState.NOREGISTER) + } + }} + /> + } + label={ + + Продължаване без регистрация + + + + + + + } + /> + )} ) } From e50c9d7de479a66fc772d2eb46317be860e23490 Mon Sep 17 00:00:00 2001 From: "dimitar.nizamov" Date: Mon, 23 Jan 2023 21:29:23 +0100 Subject: [PATCH 59/78] feat-7134 - return the authentication on null if checkbox is unchecekd --- .../donation-flow/steps/authentication/Authentication.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/donation-flow/steps/authentication/Authentication.tsx b/src/components/donation-flow/steps/authentication/Authentication.tsx index be80a1db5..66fccaa58 100644 --- a/src/components/donation-flow/steps/authentication/Authentication.tsx +++ b/src/components/donation-flow/steps/authentication/Authentication.tsx @@ -78,7 +78,9 @@ export default function Authentication() { onChange={(_, checked) => { if (checked) { setFieldValue('authentication', DonationFormDataAuthState.NOREGISTER) + return } + setFieldValue('authentication', null) }} /> } From 5c833b9099eed2ffe7b5feeeef44a85de795883a Mon Sep 17 00:00:00 2001 From: Dimitar Nizamov Date: Tue, 24 Jan 2023 13:11:31 +0100 Subject: [PATCH 60/78] avatar-letters-hotfix - add a fallback to `given_name` and `family_name` avatar letters (#1300) --- src/components/layout/nav/PrivateMenu.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/layout/nav/PrivateMenu.tsx b/src/components/layout/nav/PrivateMenu.tsx index 86fa6dbbd..9bbd4cbf2 100644 --- a/src/components/layout/nav/PrivateMenu.tsx +++ b/src/components/layout/nav/PrivateMenu.tsx @@ -61,9 +61,9 @@ export default function PrivateMenu() { } const title = `${session?.user?.name}\n(${session?.user?.email})` - const lettersAvatar = `${session.user?.given_name.charAt(0)}${session.user?.family_name.charAt( - 0, - )}`.toUpperCase() + const lettersAvatar = `${session.user?.given_name?.charAt(0) || session.user?.email?.charAt(0)}${ + session.user?.family_name?.charAt(0) || session.user?.email?.charAt(1) + }`.toUpperCase() return ( From d10bb4500f73cc1053537184a01774252297f345 Mon Sep 17 00:00:00 2001 From: Dimitar Nizamov Date: Wed, 25 Jan 2023 21:02:08 +0100 Subject: [PATCH 61/78] =?UTF-8?q?put=20campaigns=20with=20status=20differe?= =?UTF-8?q?nt=20than=20active=20at=20the=20bottom=20of=20the=20=E2=80=A6?= =?UTF-8?q?=20(#1304)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * put campaigns with status different than active at the bottom of the list * remove left out `console.log` --- src/common/hooks/campaigns.ts | 33 +++++++++++++++---- src/components/campaigns/CampaignFilter.tsx | 1 - .../CampaignsSection/CampaignsSection.tsx | 1 - src/pages/campaigns/index.tsx | 4 +-- src/pages/index.tsx | 6 ++-- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/common/hooks/campaigns.ts b/src/common/hooks/campaigns.ts index 9535a89fa..f855e438f 100644 --- a/src/common/hooks/campaigns.ts +++ b/src/common/hooks/campaigns.ts @@ -14,16 +14,37 @@ import { DonationStatus } from 'gql/donations.enums' import { apiClient } from 'service/apiClient' // NOTE: shuffling the campaigns so that each gets its fair chance to be on top row -const shuffleQueryFn: QueryFunction = async ({ queryKey }) => { +export const campaignsOrderQueryFunction: QueryFunction = async ({ + queryKey, +}) => { const response = await apiClient.get(queryKey.join('/')) - return shuffle(response.data) + // Put the campaigns in 2 arrays, one for active and one for inactive + const activeCampaigns: CampaignResponse[] = [] + const inactiveCampaigns: CampaignResponse[] = [] + response.data.forEach((campaign: CampaignResponse) => { + if (campaign.state === 'active') { + activeCampaigns.push(campaign) + } else { + inactiveCampaigns.push(campaign) + } + }) + // Shuffle the active campaigns + const shuffledActiveCampaigns = shuffle(activeCampaigns) + // Shuffle the inactive campaigns + const shuffledInactiveCampaigns = shuffle(inactiveCampaigns) + // Concatenate the two arrays + return shuffledActiveCampaigns.concat(shuffledInactiveCampaigns) } export function useCampaignList() { - return useQuery([endpoints.campaign.listCampaigns.url], shuffleQueryFn, { - // Add 15 minutes of cache time - staleTime: 1000 * 60 * 15, - }) + return useQuery( + [endpoints.campaign.listCampaigns.url], + campaignsOrderQueryFunction, + { + // Add 15 minutes of cache time + staleTime: 1000 * 60 * 15, + }, + ) } export function useCampaignAdminList() { diff --git a/src/components/campaigns/CampaignFilter.tsx b/src/components/campaigns/CampaignFilter.tsx index 632abb21e..62bef7ed5 100644 --- a/src/components/campaigns/CampaignFilter.tsx +++ b/src/components/campaigns/CampaignFilter.tsx @@ -76,7 +76,6 @@ export default function CampaignFilter() { const { mobile } = useMobile() const { data: campaigns, isLoading } = useCampaignList() const [selectedCategory, setSelectedCategory] = useState('ALL') - // TODO: add filters&sorting of campaigns so people can select based on personal preferences const campaignToShow = useMemo(() => { const filteredCampaigns = diff --git a/src/components/index/sections/CampaignsSection/CampaignsSection.tsx b/src/components/index/sections/CampaignsSection/CampaignsSection.tsx index ccc97b89a..83069f5d6 100644 --- a/src/components/index/sections/CampaignsSection/CampaignsSection.tsx +++ b/src/components/index/sections/CampaignsSection/CampaignsSection.tsx @@ -12,7 +12,6 @@ import { Root, UrgentCampaignsHeading } from './CampaignsSection.styled' export default function CampaignsSection() { const { data } = useCampaignList() const { t } = useTranslation('index') - if (data === undefined) { return null } else { diff --git a/src/pages/campaigns/index.tsx b/src/pages/campaigns/index.tsx index a5f7db58d..cb4470f98 100644 --- a/src/pages/campaigns/index.tsx +++ b/src/pages/campaigns/index.tsx @@ -3,16 +3,16 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { dehydrate, QueryClient } from '@tanstack/react-query' import CampaignsPage from 'components/campaigns/CampaignsPage' +import { campaignsOrderQueryFunction } from 'common/hooks/campaigns' import { prefetchCampaignTypesList } from 'service/campaignTypes' import { endpoints } from 'service/apiEndpoints' -import { queryFnFactory } from 'service/restRequests' import { CampaignResponse } from 'gql/campaigns' export const getServerSideProps: GetServerSideProps = async (params) => { const client = new QueryClient() await client.prefetchQuery( [endpoints.campaign.listCampaigns.url], - queryFnFactory(), + campaignsOrderQueryFunction, ) await prefetchCampaignTypesList(client) return { diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 6d5490005..890272eed 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,12 +3,12 @@ import { GetServerSideProps } from 'next' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' import { QueryClient } from '@tanstack/react-query' +import IndexPage from 'components/index/IndexPage' +import { campaignsOrderQueryFunction } from 'common/hooks/campaigns' import { CampaignResponse } from 'gql/campaigns' import { endpoints } from 'service/apiEndpoints' -import IndexPage from 'components/index/IndexPage' import { authOptions } from './api/auth/[...nextauth]' -import { queryFnFactory } from 'service/restRequests' export const getServerSideProps: GetServerSideProps<{ session: Session | null @@ -16,7 +16,7 @@ export const getServerSideProps: GetServerSideProps<{ const client = new QueryClient() await client.prefetchQuery( [endpoints.campaign.listCampaigns.url], - queryFnFactory(), + campaignsOrderQueryFunction, ) //For getting session on server side the docs recommend using unstable_getServerSession as per From bff7c67e88b85723220c0c08fb8d4cef014d9dbb Mon Sep 17 00:00:00 2001 From: Ani Date: Thu, 26 Jan 2023 09:46:27 +0200 Subject: [PATCH 62/78] Fix Campaigns section max width and alignment on Homepage and Campaigns page (#1303) * Fix maxWidth of Campaigns section on Homepage * Fix maxWidth for Campaigns section on Campaign page * Fix alignment of campaigns page * Fix spacings on campaigns section --- src/components/campaigns/CampaignsList.tsx | 36 ++++++++++--------- src/components/campaigns/CampaignsPage.tsx | 32 +++++++++++------ .../CampaignsSection.styled.tsx | 5 +++ 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/components/campaigns/CampaignsList.tsx b/src/components/campaigns/CampaignsList.tsx index bdfb8a7c2..a1bcb3399 100644 --- a/src/components/campaigns/CampaignsList.tsx +++ b/src/components/campaigns/CampaignsList.tsx @@ -34,22 +34,26 @@ export default function CampaignsList({ campaignToShow }: Props) { }, [campaignToShow, all]) return ( - - - {/*
{JSON.stringify(data, null, 2)}
*/} - {campaigns?.map((campaign, index, array) => ( - - ({ - textAlign: 'center', - [theme.breakpoints.down('lg')]: { textAlign: cardAlignment(index, array) }, - [theme.breakpoints.down('md')]: { textAlign: 'center' }, - })}> - - - - ))} -
+ ({ + width: `calc(100% + ${theme.spacing(1.5)})`, + marginLeft: `-${theme.spacing(2.75)}`, + })}> + {campaigns?.map((campaign, index, array) => ( + + ({ + textAlign: 'center', + [theme.breakpoints.down('lg')]: { textAlign: cardAlignment(index, array) }, + [theme.breakpoints.down('md')]: { textAlign: 'center' }, + })}> + + + + ))} {campaignToShow && campaignToShow?.length > numberOfMinimalShownCampaigns && (