From 397b3b682670c0c414a68fc3669286d98f2909d2 Mon Sep 17 00:00:00 2001 From: hstefanova Date: Wed, 19 Apr 2023 17:18:05 +0300 Subject: [PATCH 01/14] wip --- public/locales/bg/campaigns.json | 17 +++++ src/common/theme.ts | 13 +++- .../client/campaigns/CreateCampaignSteps.tsx | 76 +++++++++++++++++++ .../client/campaigns/HSCreateCampaignPage.tsx | 11 +++ .../client/campaigns/campaigns.styled.tsx | 11 +++ src/pages/campaigns/create.tsx | 7 +- 6 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 src/components/client/campaigns/CreateCampaignSteps.tsx create mode 100644 src/components/client/campaigns/HSCreateCampaignPage.tsx create mode 100644 src/components/client/campaigns/campaigns.styled.tsx diff --git a/public/locales/bg/campaigns.json b/public/locales/bg/campaigns.json index 1ce58d879..17a58d24d 100644 --- a/public/locales/bg/campaigns.json +++ b/public/locales/bg/campaigns.json @@ -196,5 +196,22 @@ "disabled": "Деактивирана", "error": "Грешна", "deleted": "Изтрита" + }, + "steps": { + "step1": "Стъпка 1", + "step1-type": "Информация кампания", + "step1-description": "на тази стъпка трябва посочите името на вашата кампания, нейния вид, бенефициент на кампанията, необходима сума и срок на кампанията", + "step2": "Стъпка 2", + "step2-type": "Организатор", + "step2-description": "тук ще се изискат ваши лични данни, както и е необходимо да се запознаете и съгласите с “Политиката за защита на лични данни” на Подкрепи.бг", + "step3": "Стъпка 3", + "step3-type": "Описание кампания", + "step3-description": "на този етап ще опишете подоробно целта на вашата кампания, какво е правено до момента (ако има извършени дейности), да посочите свои лични истории, да посочите свой/свои гаранти.", + "step4": "Стъпка 4", + "step4-type": "Снимки/Документи", + "step4-description": "свалете основния пакет от необходими документи на Вашия компютър, попълнете, подпишете. На тази стъпка прикачете файловете . Тук също ще може да прикачите снимки и видео, свързани с вашата кампания и нейната конкретна цел.", + "step5": "Стъпка 5", + "step5-type": "Преглед кампания", + "step5-description": "на тази стъпка ще можете да прегледате своята кампания" } } diff --git a/src/common/theme.ts b/src/common/theme.ts index 57d101de2..1e9d837b7 100644 --- a/src/common/theme.ts +++ b/src/common/theme.ts @@ -7,13 +7,24 @@ import { ThemeOptions, } from '@mui/material/styles' -import { Montserrat } from '@next/font/google' +import { Montserrat, Raleway, Lato } from '@next/font/google' export const montserrat = Montserrat({ display: 'auto', subsets: ['latin', 'cyrillic'], }) +export const raleway = Raleway({ + display: 'auto', + subsets: ['latin', 'cyrillic'], +}) + +export const lato = Lato({ + display: 'auto', + subsets: ['latin'], + weight: '400', +}) + const colors = { blue: { light: '#4AC3FF', diff --git a/src/components/client/campaigns/CreateCampaignSteps.tsx b/src/components/client/campaigns/CreateCampaignSteps.tsx new file mode 100644 index 000000000..da43307e3 --- /dev/null +++ b/src/components/client/campaigns/CreateCampaignSteps.tsx @@ -0,0 +1,76 @@ +import { Heading } from './campaigns.styled.tsx' +import { styled } from '@mui/system' +import theme, { lato } from 'common/theme' +import { useTranslation } from 'next-i18next' + +// TODO: Ask if Lato is still а valid font +const StepHeading = styled('strong')(() => ({ + fontWeight: 700, + fontFamily: lato.style.fontFamily, + fontSize: theme.typography.pxToRem(16), +})) + +const Paragraph = styled('p')(() => ({ + fontFamily: lato.style.fontFamily, + fontSize: theme.typography.pxToRem(14), + margin: 0, +})) + +const Note = styled('p')(() => ({ + fontFamily: 'Raleway, sans-serif', + fontStyle: 'italic', + fontSize: theme.typography.pxToRem(14), + lineHeight: theme.typography.pxToRem(24), + letterSpacing: '-0.009em', +})) + +export default function CreateCampaignSteps() { + const { t } = useTranslation('campaigns') + + return ( + <> + Създайте кампания + + + + {t('steps.step1')} "{t('steps.step1-type')}" + + - {t('steps.step1-description')} + + + + + {t('steps.step2')} "{t('steps.step2-type')}" + {' '} + -{t('steps.step2-description')} + + + + + {t('steps.step3')} "{t('steps.step3-type')}" + {' '} + -{t('steps.step3-description')} + + + + + {t('steps.step4')} "{t('steps.step4-type')}" + {' '} + -{t('steps.step4-description')} + + + + + {t('steps.step5')} "{t('steps.step5-type')}" + {' '} + -{t('steps.step5-description')} + + + + След преглед на попълнените и изпратени данни ще бъде изискан и допълнителен пакет от + документи, който е съобразен изцяло с вида на вашата кампания. За повече информация относно + кандидатстване по кампания, моля вижте ТУК. + + + ) +} diff --git a/src/components/client/campaigns/HSCreateCampaignPage.tsx b/src/components/client/campaigns/HSCreateCampaignPage.tsx new file mode 100644 index 000000000..ee6d8a3ba --- /dev/null +++ b/src/components/client/campaigns/HSCreateCampaignPage.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import Layout from 'components/client/layout/Layout' +import CreateCampaignSteps from './CreateCampaignSteps' + +export default function CreateCampaignPage() { + return ( + + + + ) +} diff --git a/src/components/client/campaigns/campaigns.styled.tsx b/src/components/client/campaigns/campaigns.styled.tsx new file mode 100644 index 000000000..7dd88e810 --- /dev/null +++ b/src/components/client/campaigns/campaigns.styled.tsx @@ -0,0 +1,11 @@ +import { styled } from '@mui/system' +import { Typography } from '@mui/material' + +import theme from 'common/theme' + +export const Heading = styled(Typography)(() => ({ + fontWeight: 500, + marginBottom: theme.spacing(2), + fontFamily: 'Montserrat, sans-serif', + fontSize: theme.typography.pxToRem(25), +})) diff --git a/src/pages/campaigns/create.tsx b/src/pages/campaigns/create.tsx index 1a6ec0e12..ac647ffdf 100644 --- a/src/pages/campaigns/create.tsx +++ b/src/pages/campaigns/create.tsx @@ -1,5 +1,7 @@ import { GetServerSideProps } from 'next' -import CreateCampaignPage from 'components/client/campaigns/CreateCampaignPage' +// import CreateCampaignPage from 'components/client/campaigns/CreateCampaignPage' +import HSCreateCampaignPage from 'components/client/campaigns/HSCreateCampaignPage' + import { securedPropsWithTranslation } from 'middleware/auth/securedProps' import { routes } from 'common/routes' @@ -8,4 +10,5 @@ export const getServerSideProps: GetServerSideProps = securedPropsWithTranslatio routes.campaigns.create, ) -export default CreateCampaignPage +// export default CreateCampaignPage +export default HSCreateCampaignPage From 15f77df42bf9a784cfda6b36aaa5755d4659aa4e Mon Sep 17 00:00:00 2001 From: hstefanova Date: Thu, 20 Apr 2023 19:05:12 +0300 Subject: [PATCH 02/14] wip - code refactoring of the campaign filter component --- .../client/campaigns/CampaignFilter.tsx | 113 +++++++----------- .../campaigns/CreateCampaignUserType.tsx | 22 ++++ .../client/campaigns/HSCreateCampaignPage.tsx | 10 +- .../client/campaigns/ListIconButtons.tsx | 61 ++++++++++ src/gql/types.d.ts | 8 ++ 5 files changed, 146 insertions(+), 68 deletions(-) create mode 100644 src/components/client/campaigns/CreateCampaignUserType.tsx create mode 100644 src/components/client/campaigns/ListIconButtons.tsx diff --git a/src/components/client/campaigns/CampaignFilter.tsx b/src/components/client/campaigns/CampaignFilter.tsx index 2c533f613..f97af734b 100644 --- a/src/components/client/campaigns/CampaignFilter.tsx +++ b/src/components/client/campaigns/CampaignFilter.tsx @@ -1,10 +1,10 @@ import React, { useMemo, useState } from 'react' -import { styled } from '@mui/material/styles' -import { Box, CircularProgress, IconButton, ImageList, Typography } from '@mui/material' +import { Box, CircularProgress } from '@mui/material' import { useCampaignList } from 'common/hooks/campaigns' import CampaignsList from './CampaignsList' import { CampaignResponse } from 'gql/campaigns' import { CampaignTypeCategory } from 'components/common/campaign-types/categories' +import { CategoryType } from 'gql/types' import { useTranslation } from 'next-i18next' import { Apartment, @@ -20,42 +20,11 @@ import { TheaterComedy, VolunteerActivism, } from '@mui/icons-material' -import useMobile from 'common/hooks/useMobile' - -const PREFIX = 'CampaignFilter' - -const classes = { - filterButtons: `${PREFIX}-filterButtons`, -} -const Root = styled('div')(() => ({ - [`& .${classes.filterButtons}`]: { - display: 'block', - height: '80px', - borderRadius: 0, - borderBottom: '1px solid transparent', - padding: 1, - '&:active': { - color: '#4AC3FF', - borderBottom: '5px solid #4AC3FF', - }, - '&:hover': { - backgroundColor: 'white', - borderBottom: '5px solid #4AC3FF', - color: '#4AC3FF', - }, - '&:focus': { - color: '#4AC3FF', - borderBottom: '5px solid #4AC3FF', - }, - '&:selected': { - color: '#4AC3FF', - borderBottom: '5px solid #4AC3FF', - }, - }, -})) +import useMobile from 'common/hooks/useMobile' +import ListIconButtons from './ListIconButtons' -const categories: { +const categoryIcons: { [key in CampaignTypeCategory]: { icon?: React.ReactElement } } = { medical: { icon: }, @@ -68,7 +37,8 @@ const categories: { art: { icon: }, animals: { icon: }, nature: { icon: }, - others: {}, + others: { icon: }, + all: { icon: }, } export default function CampaignFilter() { @@ -76,6 +46,7 @@ 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 = @@ -88,36 +59,44 @@ export default function CampaignFilter() { return filteredCampaigns }, [campaigns, selectedCategory]) + const categories = useMemo(() => { + const computedCategories = Object.values(CampaignTypeCategory).map((category) => { + const count = + campaigns?.filter((campaign) => campaign.campaignType.category === category).length ?? 0 + + return { + type: category, + text: t(`campaigns:filters.${category}`), + count: count, + icon: categoryIcons[category].icon, + } + }) + + const allCategory = { + type: 'ALL', + text: t('campaigns:filters.all'), + count: campaigns?.length, + icon: categoryIcons.all.icon, + } + + return [...computedCategories, allCategory] + }, [campaigns]) + + const clickHandler = (category) => { + setSelectedCategory(category.type) + } + return ( - - - {Object.values(CampaignTypeCategory).map((category) => { - const count = - campaigns?.filter((campaign) => campaign.campaignType.category === category).length ?? 0 - return ( - setSelectedCategory(category)}> - {categories[category].icon ?? } - - {t(`campaigns:filters.${category}`)} ({count}) - - - ) - })} - setSelectedCategory('ALL')}> - - - {t(`campaigns:filters.all`)} ({campaigns?.length ?? 0}) - - - + <> + + + + {isLoading ? ( @@ -125,6 +104,6 @@ export default function CampaignFilter() { ) : ( )} - + ) } diff --git a/src/components/client/campaigns/CreateCampaignUserType.tsx b/src/components/client/campaigns/CreateCampaignUserType.tsx new file mode 100644 index 000000000..32bd936ff --- /dev/null +++ b/src/components/client/campaigns/CreateCampaignUserType.tsx @@ -0,0 +1,22 @@ +import { Heading } from './campaigns.styled.tsx' +import { styled } from '@mui/system' +// import theme, { lato } from 'common/theme' +import { useTranslation } from 'next-i18next' +import { Typography, IconButton, Box } from '@mui/material' +// import { PersonIcon } from '@mui/icons-material' +import PersonIcon from '@mui/icons-material/Person' +import PeopleOutlinedIcon from '@mui/icons-material/PeopleOutlined' + +import ListIconButtons from './ListIconButtons' + +// TODO: translations / array with data for the ListIconButtons +export default function CreateCampaignUserType() { + return ( + <> + Тип на вашата организация + Моля, посочете типа на вашата организация: + + {/* */} + + ) +} diff --git a/src/components/client/campaigns/HSCreateCampaignPage.tsx b/src/components/client/campaigns/HSCreateCampaignPage.tsx index ee6d8a3ba..c0fd19035 100644 --- a/src/components/client/campaigns/HSCreateCampaignPage.tsx +++ b/src/components/client/campaigns/HSCreateCampaignPage.tsx @@ -1,11 +1,19 @@ +import { Box } from '@mui/material' + import React from 'react' import Layout from 'components/client/layout/Layout' import CreateCampaignSteps from './CreateCampaignSteps' +import CreateCampaignUserType from './CreateCampaignUserType' export default function CreateCampaignPage() { return ( - + + + + + + ) } diff --git a/src/components/client/campaigns/ListIconButtons.tsx b/src/components/client/campaigns/ListIconButtons.tsx new file mode 100644 index 000000000..91aa22d72 --- /dev/null +++ b/src/components/client/campaigns/ListIconButtons.tsx @@ -0,0 +1,61 @@ +import { styled } from '@mui/material/styles' +import { IconButton, ImageList, Typography } from '@mui/material' +import { Category } from '@mui/icons-material' + +const Root = styled('div')(() => ({ + [`& .iconButton`]: { + display: 'block', + height: '80px', + borderRadius: 0, + borderBottom: '1px solid transparent', + position: 'relative', + '&:before': { + content: '""', + position: 'absolute', + bottom: '0', + left: '0', + width: '100%', + height: '0', + backgroundColor: '#4AC3FF', + transition: 'height .2s', + }, + '&:active': { + color: '#4AC3FF', + }, + '&:active:before': { + height: '5px', + }, + '&:hover': { + backgroundColor: 'white', + color: '#4AC3FF', + }, + '&:hover:before': { + height: '5px', + }, + '&:focus': { + color: '#4AC3FF', + }, + '&:selected': { + color: '#4AC3FF', + }, + }, +})) + +export default function ListIconButtons({ data, onClick, cols, rowHeight, gap, style }) { + return ( + + + {data.map((item) => { + return ( + onClick(item)}> + {item.icon ?? } + + {item.text} ({item.count}) + + + ) + })} + + + ) +} diff --git a/src/gql/types.d.ts b/src/gql/types.d.ts index 4e501a984..6e83fffbc 100644 --- a/src/gql/types.d.ts +++ b/src/gql/types.d.ts @@ -1,3 +1,5 @@ +import React from 'react' + export type UUID = string export type PaginationData = { @@ -10,3 +12,9 @@ export type FilterData = { type: DonationType date: { from: Date | null; to: Date | null } } + +export type CategoryType = { + type: string + count?: number + icon: React.ReactElement +} From 6696db7780f52763645775dc6d466798f1bbfa27 Mon Sep 17 00:00:00 2001 From: hstefanova Date: Fri, 21 Apr 2023 20:11:33 +0300 Subject: [PATCH 03/14] wip - fixes on ListIconButton component and translations --- public/locales/bg/campaigns.json | 3 +- public/locales/en/campaigns.json | 4 ++- .../client/campaigns/CampaignFilter.tsx | 2 ++ .../client/campaigns/CreateCampaignSteps.tsx | 12 +++++-- .../campaigns/CreateCampaignUserType.tsx | 15 ++++++++- .../client/campaigns/ListIconButtons.tsx | 31 ++++++++++--------- src/gql/types.d.ts | 1 + 7 files changed, 48 insertions(+), 20 deletions(-) diff --git a/public/locales/bg/campaigns.json b/public/locales/bg/campaigns.json index 17a58d24d..4bb1b00bc 100644 --- a/public/locales/bg/campaigns.json +++ b/public/locales/bg/campaigns.json @@ -213,5 +213,6 @@ "step5": "Стъпка 5", "step5-type": "Преглед кампания", "step5-description": "на тази стъпка ще можете да прегледате своята кампания" - } + }, + "note": "След преглед на попълнените и изпратени данни ще бъде изискан и допълнителен пакет от документи, който е съобразен изцяло с вида на вашата кампания. За повече информация относно кандидатстване по кампания, моля вижте" } diff --git a/public/locales/en/campaigns.json b/public/locales/en/campaigns.json index d2246deec..4a162a4f2 100644 --- a/public/locales/en/campaigns.json +++ b/public/locales/en/campaigns.json @@ -190,5 +190,7 @@ "disabled": "Disabled", "error": "Error", "deleted": "Deleted" - } + }, + "steps": {}, + "note": {} } diff --git a/src/components/client/campaigns/CampaignFilter.tsx b/src/components/client/campaigns/CampaignFilter.tsx index f97af734b..7206155a4 100644 --- a/src/components/client/campaigns/CampaignFilter.tsx +++ b/src/components/client/campaigns/CampaignFilter.tsx @@ -69,6 +69,7 @@ export default function CampaignFilter() { text: t(`campaigns:filters.${category}`), count: count, icon: categoryIcons[category].icon, + isDisabled: !count, } }) @@ -77,6 +78,7 @@ export default function CampaignFilter() { text: t('campaigns:filters.all'), count: campaigns?.length, icon: categoryIcons.all.icon, + isDisabled: false, } return [...computedCategories, allCategory] diff --git a/src/components/client/campaigns/CreateCampaignSteps.tsx b/src/components/client/campaigns/CreateCampaignSteps.tsx index da43307e3..822074464 100644 --- a/src/components/client/campaigns/CreateCampaignSteps.tsx +++ b/src/components/client/campaigns/CreateCampaignSteps.tsx @@ -2,8 +2,12 @@ import { Heading } from './campaigns.styled.tsx' import { styled } from '@mui/system' import theme, { lato } from 'common/theme' import { useTranslation } from 'next-i18next' +import Link from 'next/link' // TODO: Ask if Lato is still а valid font +// TODO: Translate in english the steps +// TODO: Ask for "CLICK HERE" link in Note component + const StepHeading = styled('strong')(() => ({ fontWeight: 700, fontFamily: lato.style.fontFamily, @@ -24,6 +28,10 @@ const Note = styled('p')(() => ({ letterSpacing: '-0.009em', })) +const NoteLink = styled('a')(() => ({ + color: 'red', +})) + export default function CreateCampaignSteps() { const { t } = useTranslation('campaigns') @@ -67,9 +75,7 @@ export default function CreateCampaignSteps() { - След преглед на попълнените и изпратени данни ще бъде изискан и допълнителен пакет от - документи, който е съобразен изцяло с вида на вашата кампания. За повече информация относно - кандидатстване по кампания, моля вижте ТУК. + {t('note')} ТУК. ) diff --git a/src/components/client/campaigns/CreateCampaignUserType.tsx b/src/components/client/campaigns/CreateCampaignUserType.tsx index 32bd936ff..a510a7399 100644 --- a/src/components/client/campaigns/CreateCampaignUserType.tsx +++ b/src/components/client/campaigns/CreateCampaignUserType.tsx @@ -9,6 +9,19 @@ import PeopleOutlinedIcon from '@mui/icons-material/PeopleOutlined' import ListIconButtons from './ListIconButtons' +const users = [ + { + type: 'individual', + text: 'individual', + icon: , + }, + { + type: 'organization', + text: 'organization', + icon: , + }, +] + // TODO: translations / array with data for the ListIconButtons export default function CreateCampaignUserType() { return ( @@ -16,7 +29,7 @@ export default function CreateCampaignUserType() { Тип на вашата организация Моля, посочете типа на вашата организация: - {/* */} + ) } diff --git a/src/components/client/campaigns/ListIconButtons.tsx b/src/components/client/campaigns/ListIconButtons.tsx index 91aa22d72..047db0895 100644 --- a/src/components/client/campaigns/ListIconButtons.tsx +++ b/src/components/client/campaigns/ListIconButtons.tsx @@ -1,11 +1,11 @@ import { styled } from '@mui/material/styles' -import { IconButton, ImageList, Typography } from '@mui/material' +import { IconButton, ImageList, ImageListItem, Typography } from '@mui/material' import { Category } from '@mui/icons-material' const Root = styled('div')(() => ({ [`& .iconButton`]: { display: 'block', - height: '80px', + height: '90px', borderRadius: 0, borderBottom: '1px solid transparent', position: 'relative', @@ -19,12 +19,7 @@ const Root = styled('div')(() => ({ backgroundColor: '#4AC3FF', transition: 'height .2s', }, - '&:active': { - color: '#4AC3FF', - }, - '&:active:before': { - height: '5px', - }, + '&:hover': { backgroundColor: 'white', color: '#4AC3FF', @@ -46,13 +41,21 @@ export default function ListIconButtons({ data, onClick, cols, rowHeight, gap, s {data.map((item) => { + const hasCountProperty = Object.keys(item).includes('count') + return ( - onClick(item)}> - {item.icon ?? } - - {item.text} ({item.count}) - - + + onClick(item)}> + {item.icon ?? } + + + {item.text} {hasCountProperty && `(${item.count})`} + + + ) })} diff --git a/src/gql/types.d.ts b/src/gql/types.d.ts index 6e83fffbc..6f6414b3c 100644 --- a/src/gql/types.d.ts +++ b/src/gql/types.d.ts @@ -14,6 +14,7 @@ export type FilterData = { } export type CategoryType = { + text: string type: string count?: number icon: React.ReactElement From 5e32607ff005151c23d6f5b602e3b67b5f62ca4f Mon Sep 17 00:00:00 2001 From: hstefanova Date: Tue, 25 Apr 2023 17:42:04 +0300 Subject: [PATCH 04/14] wip - add translations and some UI fixes --- public/locales/bg/campaigns.json | 21 ++++++-- public/locales/en/campaigns.json | 31 +++++++++++- .../client/campaigns/CampaignFilter.tsx | 7 ++- .../client/campaigns/CreateCampaignSteps.tsx | 29 ++++++----- .../campaigns/CreateCampaignUserType.tsx | 49 ++++++++++--------- .../client/campaigns/HSCreateCampaignPage.tsx | 36 ++++++++++++-- .../client/campaigns/ListIconButtons.tsx | 24 +++++++-- src/components/client/layout/Layout.tsx | 4 +- src/gql/types.d.ts | 1 + 9 files changed, 148 insertions(+), 54 deletions(-) diff --git a/public/locales/bg/campaigns.json b/public/locales/bg/campaigns.json index 4bb1b00bc..26e81a021 100644 --- a/public/locales/bg/campaigns.json +++ b/public/locales/bg/campaigns.json @@ -197,22 +197,33 @@ "error": "Грешна", "deleted": "Изтрита" }, + "create-campaign": "Създайте кампания", "steps": { "step1": "Стъпка 1", - "step1-type": "Информация кампания", + "step1-type": "Информация за кампания", "step1-description": "на тази стъпка трябва посочите името на вашата кампания, нейния вид, бенефициент на кампанията, необходима сума и срок на кампанията", "step2": "Стъпка 2", "step2-type": "Организатор", "step2-description": "тук ще се изискат ваши лични данни, както и е необходимо да се запознаете и съгласите с “Политиката за защита на лични данни” на Подкрепи.бг", "step3": "Стъпка 3", - "step3-type": "Описание кампания", + "step3-type": "Описание на кампания", "step3-description": "на този етап ще опишете подоробно целта на вашата кампания, какво е правено до момента (ако има извършени дейности), да посочите свои лични истории, да посочите свой/свои гаранти.", "step4": "Стъпка 4", "step4-type": "Снимки/Документи", - "step4-description": "свалете основния пакет от необходими документи на Вашия компютър, попълнете, подпишете. На тази стъпка прикачете файловете . Тук също ще може да прикачите снимки и видео, свързани с вашата кампания и нейната конкретна цел.", + "step4-description-part1": "свалете основния пакет от", + "step4-description-link": "необходими документи", + "step4-description-part2": "на Вашия компютър, попълнете, подпишете. На тази стъпка прикачете файловете . Тук също ще може да прикачите снимки и видео, свързани с вашата кампания и нейната конкретна цел.", "step5": "Стъпка 5", - "step5-type": "Преглед кампания", + "step5-type": "Преглед на кампания", "step5-description": "на тази стъпка ще можете да прегледате своята кампания" }, - "note": "След преглед на попълнените и изпратени данни ще бъде изискан и допълнителен пакет от документи, който е съобразен изцяло с вида на вашата кампания. За повече информация относно кандидатстване по кампания, моля вижте" + "here": "тук", + "note": "След преглед на попълнените и изпратени данни ще бъде изискан и допълнителен пакет от документи, който е съобразен изцяло с вида на вашата кампания. За повече информация относно кандидатстване по кампания, моля вижте", + "type-of-organization": "Тип на вашата организация", + "please-select-organization": "Моля, посочете типа на вашата организация:", + "individual": "Физическо лице", + "organization": "Организация", + "back": "Назад", + "next": "Продължете", + "save-and-next": "Запазете и продължете" } diff --git a/public/locales/en/campaigns.json b/public/locales/en/campaigns.json index 4a162a4f2..189e10856 100644 --- a/public/locales/en/campaigns.json +++ b/public/locales/en/campaigns.json @@ -191,6 +191,33 @@ "error": "Error", "deleted": "Deleted" }, - "steps": {}, - "note": {} + "create-campaign": "Create campaign", + "steps": { + "step1": "Step 1", + "step1-type": "Campaign information", + "step1-description": "at this step you must specify the name of your campaign, its type, beneficiary of the campaign, required amount and duration of the campaign", + "step2": "Step 2", + "step2-type": "Organizer", + "step2-description": "here your personal data will be required, as well as it is necessary to familiarize yourself with and agree to the 'Privacy policy' of Podkrepi.bg", + "step3": "Step 3", + "step3-type": "Campaign description", + "step3-description": "at this step you will describe in detail the purpose of your campaign, what has been done so far (if any activities have been carried out), indicate your personal stories, indicate your guarantor(s).", + "step4": "Step 4", + "step4-type": "Photos/Documents", + "step4-description-part1": "download the core package from", + "step4-description-link": "necessary documents", + "step4-description-part2": "on your computer, fill in, sign. At this step, attach the files . Here you will also be able to attach photos and video related to your campaign and its specific purpose.", + "step5": "Step 5", + "step5-type": "Campaign overview", + "step5-description": "at this step you will be able to review your campaign" + }, + "here": "here", + "note": "After reviewing the completed and sent data, an additional package of documents will be requested, which is fully tailored to the type of your campaign. For more information on applying by campaign, please click", + "type-of-organization": "Type of your organization", + "please-select-organization": "Please select your organization type:", + "individual": "Individual", + "organization": "Organization", + "back": "Back", + "next": "Continue", + "save-and-next": "Save & Continue" } diff --git a/src/components/client/campaigns/CampaignFilter.tsx b/src/components/client/campaigns/CampaignFilter.tsx index 7206155a4..8ba245e6b 100644 --- a/src/components/client/campaigns/CampaignFilter.tsx +++ b/src/components/client/campaigns/CampaignFilter.tsx @@ -38,7 +38,6 @@ const categoryIcons: { animals: { icon: }, nature: { icon: }, others: { icon: }, - all: { icon: }, } export default function CampaignFilter() { @@ -77,15 +76,15 @@ export default function CampaignFilter() { type: 'ALL', text: t('campaigns:filters.all'), count: campaigns?.length, - icon: categoryIcons.all.icon, + icon: , isDisabled: false, } return [...computedCategories, allCategory] }, [campaigns]) - const clickHandler = (category) => { - setSelectedCategory(category.type) + const clickHandler = (category: CategoryType) => { + setSelectedCategory(category?.type) } return ( diff --git a/src/components/client/campaigns/CreateCampaignSteps.tsx b/src/components/client/campaigns/CreateCampaignSteps.tsx index 822074464..770b2164f 100644 --- a/src/components/client/campaigns/CreateCampaignSteps.tsx +++ b/src/components/client/campaigns/CreateCampaignSteps.tsx @@ -1,12 +1,11 @@ -import { Heading } from './campaigns.styled.tsx' +import { Heading } from './campaigns.styled' import { styled } from '@mui/system' import theme, { lato } from 'common/theme' import { useTranslation } from 'next-i18next' -import Link from 'next/link' -// TODO: Ask if Lato is still а valid font -// TODO: Translate in english the steps -// TODO: Ask for "CLICK HERE" link in Note component +import ExternalLink from 'components/common/ExternalLink' + +// TODO: Ask for the URLs const StepHeading = styled('strong')(() => ({ fontWeight: 700, @@ -18,6 +17,11 @@ const Paragraph = styled('p')(() => ({ fontFamily: lato.style.fontFamily, fontSize: theme.typography.pxToRem(14), margin: 0, + + '& a': { + fontStyle: 'normal', + textDecoration: 'underline', + }, })) const Note = styled('p')(() => ({ @@ -26,10 +30,11 @@ const Note = styled('p')(() => ({ fontSize: theme.typography.pxToRem(14), lineHeight: theme.typography.pxToRem(24), letterSpacing: '-0.009em', -})) -const NoteLink = styled('a')(() => ({ - color: 'red', + '& a': { + fontStyle: 'normal', + textDecoration: 'underline', + }, })) export default function CreateCampaignSteps() { @@ -37,7 +42,7 @@ export default function CreateCampaignSteps() { return ( <> - Създайте кампания + {t('create-campaign')} @@ -64,7 +69,9 @@ export default function CreateCampaignSteps() { {t('steps.step4')} "{t('steps.step4-type')}" {' '} - -{t('steps.step4-description')} + -{t('steps.step4-description-part1')} +   {t('steps.step4-description-link')}  + {t('steps.step4-description-part2')} @@ -75,7 +82,7 @@ export default function CreateCampaignSteps() { - {t('note')} ТУК. + {t('note')} {t('here')}. ) diff --git a/src/components/client/campaigns/CreateCampaignUserType.tsx b/src/components/client/campaigns/CreateCampaignUserType.tsx index a510a7399..bf3732c42 100644 --- a/src/components/client/campaigns/CreateCampaignUserType.tsx +++ b/src/components/client/campaigns/CreateCampaignUserType.tsx @@ -1,35 +1,38 @@ -import { Heading } from './campaigns.styled.tsx' -import { styled } from '@mui/system' -// import theme, { lato } from 'common/theme' +import { Heading } from './campaigns.styled' import { useTranslation } from 'next-i18next' -import { Typography, IconButton, Box } from '@mui/material' -// import { PersonIcon } from '@mui/icons-material' +import { Typography } from '@mui/material' +import { CategoryType } from 'gql/types' + import PersonIcon from '@mui/icons-material/Person' import PeopleOutlinedIcon from '@mui/icons-material/PeopleOutlined' - import ListIconButtons from './ListIconButtons' -const users = [ - { - type: 'individual', - text: 'individual', - icon: , - }, - { - type: 'organization', - text: 'organization', - icon: , - }, -] +type Props = { + onClick: (item: CategoryType) => void +} + +export default function CreateCampaignUserType({ onClick }: Props) { + const { t } = useTranslation('campaigns') + + const users = [ + { + type: 'individual', + text: t('individual'), + icon: , + }, + { + type: 'organization', + text: t('organization'), + icon: , + }, + ] -// TODO: translations / array with data for the ListIconButtons -export default function CreateCampaignUserType() { return ( <> - Тип на вашата организация - Моля, посочете типа на вашата организация: + {t('type-of-organization')} + {t('please-select-organization')} - + ) } diff --git a/src/components/client/campaigns/HSCreateCampaignPage.tsx b/src/components/client/campaigns/HSCreateCampaignPage.tsx index c0fd19035..076f6fb30 100644 --- a/src/components/client/campaigns/HSCreateCampaignPage.tsx +++ b/src/components/client/campaigns/HSCreateCampaignPage.tsx @@ -1,18 +1,48 @@ -import { Box } from '@mui/material' +import { Box, Grid, Button } from '@mui/material' +import { CategoryType } from 'gql/types' +import { useTranslation } from 'next-i18next' +import { useRouter } from 'next/router' import React from 'react' import Layout from 'components/client/layout/Layout' import CreateCampaignSteps from './CreateCampaignSteps' import CreateCampaignUserType from './CreateCampaignUserType' +import ChevronRightIcon from '@mui/icons-material/ChevronRight' export default function CreateCampaignPage() { + const { t } = useTranslation('campaigns') + const router = useRouter() + + const userTypeHandler = (user: CategoryType) => { + // TODO: store the selection on redirect + console.log('selected user type: ', user) + } + + const goToNextPage = () => router.push('/about') + const goBackToPrevPage = () => router.back() + return ( - + - + + + + + + + + + + + + ) diff --git a/src/components/client/campaigns/ListIconButtons.tsx b/src/components/client/campaigns/ListIconButtons.tsx index 047db0895..c4e323d54 100644 --- a/src/components/client/campaigns/ListIconButtons.tsx +++ b/src/components/client/campaigns/ListIconButtons.tsx @@ -1,6 +1,7 @@ import { styled } from '@mui/material/styles' import { IconButton, ImageList, ImageListItem, Typography } from '@mui/material' import { Category } from '@mui/icons-material' +import { CategoryType } from 'gql/types' const Root = styled('div')(() => ({ [`& .iconButton`]: { @@ -30,13 +31,26 @@ const Root = styled('div')(() => ({ '&:focus': { color: '#4AC3FF', }, - '&:selected': { - color: '#4AC3FF', + '&:focus:before': { + height: '5px', }, }, })) -export default function ListIconButtons({ data, onClick, cols, rowHeight, gap, style }) { +const IconButtonText = styled(Typography)(() => ({ + fontFamily: 'Raleway, sans-serif', +})) + +type Props = { + data: CategoryType[] + onClick: (item: CategoryType) => void + cols?: number + rowHeight?: number + gap?: number + style?: React.CSSProperties +} + +export default function ListIconButtons({ data, onClick, cols, rowHeight, gap, style }: Props) { return ( @@ -51,9 +65,9 @@ export default function ListIconButtons({ data, onClick, cols, rowHeight, gap, s onClick={() => onClick(item)}> {item.icon ?? } - + {item.text} {hasCountProperty && `(${item.count})`} - + ) diff --git a/src/components/client/layout/Layout.tsx b/src/components/client/layout/Layout.tsx index fbb64ebf8..3e5ebb98d 100644 --- a/src/components/client/layout/Layout.tsx +++ b/src/components/client/layout/Layout.tsx @@ -28,6 +28,7 @@ type LayoutProps = React.PropsWithChildren< boxProps?: BoxProps metaTitle?: string metaDescription?: string + minHeight?: string profilePage?: boolean canonicalUrl?: string prevPage?: string @@ -40,6 +41,7 @@ export default function Layout({ ogImage, children, maxWidth = 'lg', + minHeight = '100vh', disableOffset = false, hideFooter = false, canonicalUrl, @@ -65,7 +67,7 @@ export default function Layout({ disableGutters sx={{ backgroundColor: profilePage ? '#E9F6FF' : '' }}> diff --git a/src/gql/types.d.ts b/src/gql/types.d.ts index 6f6414b3c..5a6fca23c 100644 --- a/src/gql/types.d.ts +++ b/src/gql/types.d.ts @@ -17,5 +17,6 @@ export type CategoryType = { text: string type: string count?: number + isDisabled?: boolean icon: React.ReactElement } From a888dc51f61140829f60ec4c16eb1a339733ee81 Mon Sep 17 00:00:00 2001 From: hstefanova Date: Wed, 26 Apr 2023 18:48:17 +0300 Subject: [PATCH 05/14] wip - create custom stepper --- .../admin/campaigns/grid/HSCreateForm.tsx | 331 ++++++++++++++++++ .../campaigns/CustomHorizontalStepper.tsx | 130 +++++++ .../client/campaigns/HSCreateCampaignPage.tsx | 3 +- .../campaigns/HSCreateCampaignStepsPage.tsx | 56 +++ .../{create.tsx => create/index.tsx} | 2 - src/pages/campaigns/create/steps.tsx | 12 + 6 files changed, 531 insertions(+), 3 deletions(-) create mode 100644 src/components/admin/campaigns/grid/HSCreateForm.tsx create mode 100644 src/components/client/campaigns/CustomHorizontalStepper.tsx create mode 100644 src/components/client/campaigns/HSCreateCampaignStepsPage.tsx rename src/pages/campaigns/{create.tsx => create/index.tsx} (78%) create mode 100644 src/pages/campaigns/create/steps.tsx diff --git a/src/components/admin/campaigns/grid/HSCreateForm.tsx b/src/components/admin/campaigns/grid/HSCreateForm.tsx new file mode 100644 index 000000000..b5d70bd9c --- /dev/null +++ b/src/components/admin/campaigns/grid/HSCreateForm.tsx @@ -0,0 +1,331 @@ +import * as yup from 'yup' +import { useState } from 'react' +import { useRouter } from 'next/router' +import { FormikHelpers } from 'formik' +import { useMutation } from '@tanstack/react-query' +import { useTranslation } from 'next-i18next' +import { format, parse, isDate } from 'date-fns' +import { AxiosError, AxiosResponse } from 'axios' +import { Button, Grid, Typography } from '@mui/material' +import Link from 'next/link' + +import { routes } from 'common/routes' +import { Currency } from 'gql/currency' +import { AlertStore } from 'stores/AlertStore' +import { createSlug } from 'common/util/createSlug' +import FileList from 'components/common/file-upload/FileList' +import FileUpload from 'components/common/file-upload/FileUpload' +import GenericForm from 'components/common/form/GenericForm' +import SubmitButton from 'components/common/form/SubmitButton' +import FormTextField from 'components/common/form/FormTextField' +import AcceptTermsField from 'components/common/form/AcceptTermsField' + +import dynamic from 'next/dynamic' +const FormRichTextField = dynamic(() => import('components/common/form/FormRichTextField'), { + ssr: false, +}) + +import { ApiErrors, handleUniqueViolation, isAxiosError, matchValidator } from 'service/apiErrors' +import { useCreateCampaign, useUploadCampaignFiles } from 'service/campaign' +import { + CampaignFileRole, + FileRole, + UploadCampaignFiles, +} from 'components/common/campaign-file/roles' +import AcceptPrivacyPolicyField from 'components/common/form/AcceptPrivacyPolicyField' +import { + CampaignResponse, + CampaignInput, + CampaignUploadImage, + CampaignAdminCreateFormData, +} from 'gql/campaigns' + +import CampaignTypeSelect from '../../../client/campaigns/CampaignTypeSelect' +import CoordinatorSelect from './CoordinatorSelect' +import BeneficiarySelect from './BeneficiarySelect' +import { CampaignState } from '../../../client/campaigns/helpers/campaign.enums' +import { toMoney } from 'common/util/money' +import CurrencySelect from 'components/common/currency/CurrencySelect' +import OrganizerSelect from './OrganizerSelect' +import AllowDonationOnComplete from 'components/common/form/AllowDonationOnComplete' + +const formatString = 'yyyy-MM-dd' + +const parseDateString = (value: string, originalValue: string) => { + const parsedDate = isDate(originalValue) + ? originalValue + : parse(originalValue, formatString, new Date()) + + return parsedDate +} + +const validationSchema: yup.SchemaOf = yup + .object() + .defined() + .shape({ + title: yup.string().trim().min(10).max(200).required(), + slug: yup.string().trim().min(10).max(200).optional(), + description: yup.string().trim().min(50).max(60000).required(), + targetAmount: yup.number().integer().positive().required(), + allowDonationOnComplete: yup.bool().optional(), + campaignTypeId: yup.string().uuid().required(), + beneficiaryId: yup.string().uuid().required(), + coordinatorId: yup.string().uuid().required(), + organizerId: yup.string().uuid().required(), + startDate: yup.date().transform(parseDateString).required(), + state: yup.mixed().oneOf(Object.values(CampaignState)).required(), + endDate: yup + .date() + .transform(parseDateString) + .min(yup.ref('startDate'), `end date can't be before start date`), + terms: yup.bool().required().oneOf([true], 'validation:terms-of-use'), + gdpr: yup.bool().required().oneOf([true], 'validation:terms-of-service'), + currency: yup.mixed().oneOf(Object.values(Currency)).required(), + }) + +const defaults: CampaignAdminCreateFormData = { + title: '', + slug: '', + campaignTypeId: '', + beneficiaryId: '', + coordinatorId: '', + organizerId: '', + targetAmount: 1000, + allowDonationOnComplete: false, + startDate: format(new Date(), formatString), + state: CampaignState.draft, + endDate: '', + description: '', + terms: false, + gdpr: false, + currency: Currency.BGN, +} + +export type CampaignFormProps = { initialValues?: CampaignAdminCreateFormData } + +export default function CampaignForm({ initialValues = defaults }: CampaignFormProps) { + const router = useRouter() + const [files, setFiles] = useState([]) + const [roles, setRoles] = useState([]) + + const { t } = useTranslation() + + const handleError = (e: AxiosError) => { + const error = e.response + + if (error?.status === 409) { + const message = error.data.message.map((el) => handleUniqueViolation(el.constraints, t)) + return AlertStore.show(message.join('/n'), 'error') + } + + AlertStore.show(t('common:alerts.error'), 'error') + } + + const mutation = useMutation< + AxiosResponse, + AxiosError, + CampaignInput + >({ + mutationFn: useCreateCampaign(), + onError: (error) => handleError(error), + onSuccess: () => AlertStore.show(t('common:alerts.message-sent'), 'success'), + }) + + const fileUploadMutation = useMutation< + AxiosResponse, + AxiosError, + UploadCampaignFiles + >({ + mutationFn: useUploadCampaignFiles(), + }) + + const onSubmit = async ( + values: CampaignAdminCreateFormData, + { setFieldError }: FormikHelpers, + ) => { + try { + const response = await mutation.mutateAsync({ + title: values.title, + slug: createSlug(values.slug || values.title), + description: values.description, + targetAmount: toMoney(values.targetAmount), + allowDonationOnComplete: values.allowDonationOnComplete, + startDate: values.startDate, + endDate: values.endDate, + state: values.state, + essence: '', + campaignTypeId: values.campaignTypeId, + beneficiaryId: values.beneficiaryId, + coordinatorId: values.coordinatorId, + organizerId: values.organizerId, + currency: values.currency.toString(), + }) + if (files.length > 0) { + await fileUploadMutation.mutateAsync({ + files, + roles, + campaignId: response.data.id, + }) + } + router.push(routes.admin.campaigns.index) + } catch (error) { + console.error(error) + if (isAxiosError(error)) { + const { response } = error as AxiosError + response?.data.message.map(({ property, constraints }) => { + setFieldError(property, t(matchValidator(constraints))) + }) + } + } + } + + return ( + + + TEST + ({ + mb: 5, + color: theme.palette.primary.dark, + textAlign: 'center', + })}> + {t('campaigns:form-heading')} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {t('campaigns:campaign.description')} + + + +

+ Select a Beneficiery or{' '} + + Create New + +

+ +
+ +

+ Select a Coordinator or{' '} + + Create New + +

+ +
+ +

+ Select an Organizer or{' '} + + Create New + +

+ +
+ + { + setFiles((prevFiles) => [...prevFiles, ...newFiles]) + setRoles((prevRoles) => [ + ...prevRoles, + ...newFiles.map((file) => ({ + file: file.name, + role: CampaignFileRole.background, + })), + ]) + }} + buttonLabel={t('campaigns:cta.add-files')} + /> + + setFiles((prevFiles) => prevFiles.filter((file) => file.name !== deletedFile.name)) + } + onSetFileRole={(file, role) => { + setRoles((prevRoles) => [ + ...prevRoles.filter((f) => f.file !== file.name), + { file: file.name, role }, + ]) + }} + /> + + + + + + + + + + + +
+
+
+ ) +} diff --git a/src/components/client/campaigns/CustomHorizontalStepper.tsx b/src/components/client/campaigns/CustomHorizontalStepper.tsx new file mode 100644 index 000000000..c06790fa6 --- /dev/null +++ b/src/components/client/campaigns/CustomHorizontalStepper.tsx @@ -0,0 +1,130 @@ +import React from 'react' +import ChevronRightIcon from '@mui/icons-material/ChevronRight' + +import { Box, Grid, Stepper, Step, StepLabel, Button, Typography } from '@mui/material' +import { useTranslation } from 'next-i18next' + +type StepType = { + label: 'string' + component: React.ReactElement +} +type StepsMap = { + [key: number]: StepType +} + +type Props = { + steps: StepsMap +} + +export default function CustomHorizontalStepper({ steps }: Props) { + const [activeStep, setActiveStep] = React.useState(0) + const [skipped, setSkipped] = React.useState(new Set()) + const { t } = useTranslation('campaigns') + + const labels = [] + for (const step of steps.values()) { + labels.push(step.label) + } + + /*** + * In case you need to have optional steps, + * you can add their index in the optionalSteps array + * **/ + const optionalSteps = [] + + const isStepOptional = (step: number) => { + return optionalSteps.includes(step) + } + + const isStepSkipped = (step: number) => { + return skipped.has(step) + } + + const handleNext = () => { + let newSkipped = skipped + if (isStepSkipped(activeStep)) { + newSkipped = new Set(newSkipped.values()) + newSkipped.delete(activeStep) + } + + setActiveStep((prevActiveStep) => prevActiveStep + 1) + setSkipped(newSkipped) + } + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1) + } + + const handleSkip = () => { + if (!isStepOptional(activeStep)) { + // You probably want to guard against something like this, + // it should never occur unless someone's actively trying to break something. + throw new Error("You can't skip a step that isn't optional.") + } + + setActiveStep((prevActiveStep) => prevActiveStep + 1) + setSkipped((prevSkipped) => { + const newSkipped = new Set(prevSkipped.values()) + newSkipped.add(activeStep) + return newSkipped + }) + } + + return ( + + + {labels.map((label, index) => { + const stepProps: { completed?: boolean } = {} + const labelProps: { + optional?: React.ReactNode + } = {} + if (isStepOptional(index)) { + labelProps.optional = Optional + } + if (isStepSkipped(index)) { + stepProps.completed = false + } + return ( + + {label} + + ) + })} + + + {activeStep === steps.size ? ( + <> + All steps completed - you're finished + + ) : ( + <> + {steps.get(activeStep).component} + + + + + + + + + {isStepOptional(activeStep) && ( + + )} + + + + + + + + + )} + + ) +} diff --git a/src/components/client/campaigns/HSCreateCampaignPage.tsx b/src/components/client/campaigns/HSCreateCampaignPage.tsx index 076f6fb30..bb3e9843a 100644 --- a/src/components/client/campaigns/HSCreateCampaignPage.tsx +++ b/src/components/client/campaigns/HSCreateCampaignPage.tsx @@ -9,6 +9,7 @@ import CreateCampaignSteps from './CreateCampaignSteps' import CreateCampaignUserType from './CreateCampaignUserType' import ChevronRightIcon from '@mui/icons-material/ChevronRight' +// TODO: MOBILE VIEW export default function CreateCampaignPage() { const { t } = useTranslation('campaigns') const router = useRouter() @@ -18,7 +19,7 @@ export default function CreateCampaignPage() { console.log('selected user type: ', user) } - const goToNextPage = () => router.push('/about') + const goToNextPage = () => router.push('/campaigns/create/steps') const goBackToPrevPage = () => router.back() return ( diff --git a/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx b/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx new file mode 100644 index 000000000..b46845594 --- /dev/null +++ b/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx @@ -0,0 +1,56 @@ +import { Box, Grid, Button, Typography } from '@mui/material' +import { useTranslation } from 'next-i18next' + +import React from 'react' +import Layout from 'components/client/layout/Layout' +import CustomHorizontalStepper from 'components/client/campaigns/CustomHorizontalStepper' + +export default function HSCreateCampaignStepsPage() { + const { t } = useTranslation() + + const steps = new Map([ + [ + 0, + { + label: t('campaigns:steps.step1-type'), + component: Text 1, + }, + ], + [ + 1, + { + label: t('campaigns:steps.step2-type'), + component: Text 2, + }, + ], + [ + 2, + { + label: t('campaigns:steps.step3-type'), + component: Text 3, + }, + ], + [ + 3, + { + label: t('campaigns:steps.step4-type'), + component: Text 4, + }, + ], + [ + 4, + { + label: t('campaigns:steps.step4-type'), + component: Text 5, + }, + ], + ]) + + return ( + + + + + + ) +} diff --git a/src/pages/campaigns/create.tsx b/src/pages/campaigns/create/index.tsx similarity index 78% rename from src/pages/campaigns/create.tsx rename to src/pages/campaigns/create/index.tsx index ac647ffdf..1a2edda62 100644 --- a/src/pages/campaigns/create.tsx +++ b/src/pages/campaigns/create/index.tsx @@ -1,5 +1,4 @@ import { GetServerSideProps } from 'next' -// import CreateCampaignPage from 'components/client/campaigns/CreateCampaignPage' import HSCreateCampaignPage from 'components/client/campaigns/HSCreateCampaignPage' import { securedPropsWithTranslation } from 'middleware/auth/securedProps' @@ -10,5 +9,4 @@ export const getServerSideProps: GetServerSideProps = securedPropsWithTranslatio routes.campaigns.create, ) -// export default CreateCampaignPage export default HSCreateCampaignPage diff --git a/src/pages/campaigns/create/steps.tsx b/src/pages/campaigns/create/steps.tsx new file mode 100644 index 000000000..8db20775c --- /dev/null +++ b/src/pages/campaigns/create/steps.tsx @@ -0,0 +1,12 @@ +import { GetServerSideProps } from 'next' +import HSCreateCampaignStepsPage from 'components/client/campaigns/HSCreateCampaignStepsPage' + +import { securedPropsWithTranslation } from 'middleware/auth/securedProps' +import { routes } from 'common/routes' + +export const getServerSideProps: GetServerSideProps = securedPropsWithTranslation( + ['common', 'auth', 'validation', 'campaigns'], + routes.campaigns.steps, +) + +export default HSCreateCampaignStepsPage From c29d8a42a6c4b4c301d683654f1f6c09715516cb Mon Sep 17 00:00:00 2001 From: hstefanova Date: Thu, 27 Apr 2023 16:04:02 +0300 Subject: [PATCH 06/14] wip - updates on types and campaignFilter component --- .../admin/campaigns/grid/HSCreateForm.tsx | 331 ------------------ .../client/campaigns/CampaignFilter.tsx | 44 +-- .../client/campaigns/CampaignsPage.tsx | 17 +- .../campaigns/CustomHorizontalStepper.tsx | 9 +- .../client/campaigns/HSCreateCampaignPage.tsx | 16 +- .../campaigns/HSCreateCampaignStepsPage.tsx | 7 +- .../client/campaigns/campaigns.styled.tsx | 8 + .../campaigns/stepOne/BeneficiarySelect.tsx | 35 ++ .../campaigns/stepOne/CoordinatorSelect.tsx | 33 ++ .../client/campaigns/stepOne/HSCreateForm.tsx | 118 +++++++ .../campaigns/stepOne/OrganizerSelect.tsx | 37 ++ .../client/layout/nav/DonationMenu.tsx | 2 +- .../common/campaign-types/categories.ts | 1 + src/gql/types.d.ts | 3 +- yarn.lock | 4 +- 15 files changed, 294 insertions(+), 371 deletions(-) delete mode 100644 src/components/admin/campaigns/grid/HSCreateForm.tsx create mode 100644 src/components/client/campaigns/stepOne/BeneficiarySelect.tsx create mode 100644 src/components/client/campaigns/stepOne/CoordinatorSelect.tsx create mode 100644 src/components/client/campaigns/stepOne/HSCreateForm.tsx create mode 100644 src/components/client/campaigns/stepOne/OrganizerSelect.tsx diff --git a/src/components/admin/campaigns/grid/HSCreateForm.tsx b/src/components/admin/campaigns/grid/HSCreateForm.tsx deleted file mode 100644 index b5d70bd9c..000000000 --- a/src/components/admin/campaigns/grid/HSCreateForm.tsx +++ /dev/null @@ -1,331 +0,0 @@ -import * as yup from 'yup' -import { useState } from 'react' -import { useRouter } from 'next/router' -import { FormikHelpers } from 'formik' -import { useMutation } from '@tanstack/react-query' -import { useTranslation } from 'next-i18next' -import { format, parse, isDate } from 'date-fns' -import { AxiosError, AxiosResponse } from 'axios' -import { Button, Grid, Typography } from '@mui/material' -import Link from 'next/link' - -import { routes } from 'common/routes' -import { Currency } from 'gql/currency' -import { AlertStore } from 'stores/AlertStore' -import { createSlug } from 'common/util/createSlug' -import FileList from 'components/common/file-upload/FileList' -import FileUpload from 'components/common/file-upload/FileUpload' -import GenericForm from 'components/common/form/GenericForm' -import SubmitButton from 'components/common/form/SubmitButton' -import FormTextField from 'components/common/form/FormTextField' -import AcceptTermsField from 'components/common/form/AcceptTermsField' - -import dynamic from 'next/dynamic' -const FormRichTextField = dynamic(() => import('components/common/form/FormRichTextField'), { - ssr: false, -}) - -import { ApiErrors, handleUniqueViolation, isAxiosError, matchValidator } from 'service/apiErrors' -import { useCreateCampaign, useUploadCampaignFiles } from 'service/campaign' -import { - CampaignFileRole, - FileRole, - UploadCampaignFiles, -} from 'components/common/campaign-file/roles' -import AcceptPrivacyPolicyField from 'components/common/form/AcceptPrivacyPolicyField' -import { - CampaignResponse, - CampaignInput, - CampaignUploadImage, - CampaignAdminCreateFormData, -} from 'gql/campaigns' - -import CampaignTypeSelect from '../../../client/campaigns/CampaignTypeSelect' -import CoordinatorSelect from './CoordinatorSelect' -import BeneficiarySelect from './BeneficiarySelect' -import { CampaignState } from '../../../client/campaigns/helpers/campaign.enums' -import { toMoney } from 'common/util/money' -import CurrencySelect from 'components/common/currency/CurrencySelect' -import OrganizerSelect from './OrganizerSelect' -import AllowDonationOnComplete from 'components/common/form/AllowDonationOnComplete' - -const formatString = 'yyyy-MM-dd' - -const parseDateString = (value: string, originalValue: string) => { - const parsedDate = isDate(originalValue) - ? originalValue - : parse(originalValue, formatString, new Date()) - - return parsedDate -} - -const validationSchema: yup.SchemaOf = yup - .object() - .defined() - .shape({ - title: yup.string().trim().min(10).max(200).required(), - slug: yup.string().trim().min(10).max(200).optional(), - description: yup.string().trim().min(50).max(60000).required(), - targetAmount: yup.number().integer().positive().required(), - allowDonationOnComplete: yup.bool().optional(), - campaignTypeId: yup.string().uuid().required(), - beneficiaryId: yup.string().uuid().required(), - coordinatorId: yup.string().uuid().required(), - organizerId: yup.string().uuid().required(), - startDate: yup.date().transform(parseDateString).required(), - state: yup.mixed().oneOf(Object.values(CampaignState)).required(), - endDate: yup - .date() - .transform(parseDateString) - .min(yup.ref('startDate'), `end date can't be before start date`), - terms: yup.bool().required().oneOf([true], 'validation:terms-of-use'), - gdpr: yup.bool().required().oneOf([true], 'validation:terms-of-service'), - currency: yup.mixed().oneOf(Object.values(Currency)).required(), - }) - -const defaults: CampaignAdminCreateFormData = { - title: '', - slug: '', - campaignTypeId: '', - beneficiaryId: '', - coordinatorId: '', - organizerId: '', - targetAmount: 1000, - allowDonationOnComplete: false, - startDate: format(new Date(), formatString), - state: CampaignState.draft, - endDate: '', - description: '', - terms: false, - gdpr: false, - currency: Currency.BGN, -} - -export type CampaignFormProps = { initialValues?: CampaignAdminCreateFormData } - -export default function CampaignForm({ initialValues = defaults }: CampaignFormProps) { - const router = useRouter() - const [files, setFiles] = useState([]) - const [roles, setRoles] = useState([]) - - const { t } = useTranslation() - - const handleError = (e: AxiosError) => { - const error = e.response - - if (error?.status === 409) { - const message = error.data.message.map((el) => handleUniqueViolation(el.constraints, t)) - return AlertStore.show(message.join('/n'), 'error') - } - - AlertStore.show(t('common:alerts.error'), 'error') - } - - const mutation = useMutation< - AxiosResponse, - AxiosError, - CampaignInput - >({ - mutationFn: useCreateCampaign(), - onError: (error) => handleError(error), - onSuccess: () => AlertStore.show(t('common:alerts.message-sent'), 'success'), - }) - - const fileUploadMutation = useMutation< - AxiosResponse, - AxiosError, - UploadCampaignFiles - >({ - mutationFn: useUploadCampaignFiles(), - }) - - const onSubmit = async ( - values: CampaignAdminCreateFormData, - { setFieldError }: FormikHelpers, - ) => { - try { - const response = await mutation.mutateAsync({ - title: values.title, - slug: createSlug(values.slug || values.title), - description: values.description, - targetAmount: toMoney(values.targetAmount), - allowDonationOnComplete: values.allowDonationOnComplete, - startDate: values.startDate, - endDate: values.endDate, - state: values.state, - essence: '', - campaignTypeId: values.campaignTypeId, - beneficiaryId: values.beneficiaryId, - coordinatorId: values.coordinatorId, - organizerId: values.organizerId, - currency: values.currency.toString(), - }) - if (files.length > 0) { - await fileUploadMutation.mutateAsync({ - files, - roles, - campaignId: response.data.id, - }) - } - router.push(routes.admin.campaigns.index) - } catch (error) { - console.error(error) - if (isAxiosError(error)) { - const { response } = error as AxiosError - response?.data.message.map(({ property, constraints }) => { - setFieldError(property, t(matchValidator(constraints))) - }) - } - } - } - - return ( - - - TEST - ({ - mb: 5, - color: theme.palette.primary.dark, - textAlign: 'center', - })}> - {t('campaigns:form-heading')} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {t('campaigns:campaign.description')} - - - -

- Select a Beneficiery or{' '} - - Create New - -

- -
- -

- Select a Coordinator or{' '} - - Create New - -

- -
- -

- Select an Organizer or{' '} - - Create New - -

- -
- - { - setFiles((prevFiles) => [...prevFiles, ...newFiles]) - setRoles((prevRoles) => [ - ...prevRoles, - ...newFiles.map((file) => ({ - file: file.name, - role: CampaignFileRole.background, - })), - ]) - }} - buttonLabel={t('campaigns:cta.add-files')} - /> - - setFiles((prevFiles) => prevFiles.filter((file) => file.name !== deletedFile.name)) - } - onSetFileRole={(file, role) => { - setRoles((prevRoles) => [ - ...prevRoles.filter((f) => f.file !== file.name), - { file: file.name, role }, - ]) - }} - /> - - - - - - - - - - - -
-
-
- ) -} diff --git a/src/components/client/campaigns/CampaignFilter.tsx b/src/components/client/campaigns/CampaignFilter.tsx index 8ba245e6b..113d47aa1 100644 --- a/src/components/client/campaigns/CampaignFilter.tsx +++ b/src/components/client/campaigns/CampaignFilter.tsx @@ -38,31 +38,47 @@ const categoryIcons: { animals: { icon: }, nature: { icon: }, others: { icon: }, + all: { icon: }, } -export default function CampaignFilter() { +type Props = { + showCampaigns?: boolean + selected: CampaignTypeCategory + onClick: (item: CategoryType) => void +} + +export default function CampaignFilter({ selected, showCampaigns = true, onClick }: Props) { const { t } = useTranslation() 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 = campaigns?.filter((campaign) => { - if (selectedCategory != 'ALL') { - return campaign.campaignType.category === selectedCategory + if (selected != 'all') { + return campaign.campaignType.category === selected } return campaign }) ?? [] return filteredCampaigns - }, [campaigns, selectedCategory]) + }, [campaigns, selected]) const categories = useMemo(() => { const computedCategories = Object.values(CampaignTypeCategory).map((category) => { const count = campaigns?.filter((campaign) => campaign.campaignType.category === category).length ?? 0 + if (category === 'all') { + return { + type: 'all', + text: t(`campaigns:filters.${category}`), + count: campaigns?.length, + icon: categoryIcons[category].icon, + isDisabled: false, + } + } + return { type: category, text: t(`campaigns:filters.${category}`), @@ -72,27 +88,15 @@ export default function CampaignFilter() { } }) - const allCategory = { - type: 'ALL', - text: t('campaigns:filters.all'), - count: campaigns?.length, - icon: , - isDisabled: false, - } - - return [...computedCategories, allCategory] + return computedCategories }, [campaigns]) - const clickHandler = (category: CategoryType) => { - setSelectedCategory(category?.type) - } - return ( <> @@ -103,7 +107,7 @@ export default function CampaignFilter() { ) : ( - + showCampaigns && )} ) diff --git a/src/components/client/campaigns/CampaignsPage.tsx b/src/components/client/campaigns/CampaignsPage.tsx index 63623cffb..851a88a6c 100644 --- a/src/components/client/campaigns/CampaignsPage.tsx +++ b/src/components/client/campaigns/CampaignsPage.tsx @@ -1,13 +1,14 @@ -import React from 'react' +import React, { useState } from 'react' import { useTranslation } from 'next-i18next' import { Grid, Typography } from '@mui/material' +import { CategoryType } from 'gql/types' +import { styled } from '@mui/material/styles' +import { CampaignTypeCategory } from 'components/common/campaign-types/categories' import CampaignFilter from './CampaignFilter' import Layout from 'components/client/layout/Layout' -import { styled } from '@mui/material/styles' - const PREFIX = 'CampaignsPage' const classes = { @@ -77,6 +78,14 @@ const Root = styled(Layout)(({ theme }) => ({ export default function CampaignsPage() { const { t } = useTranslation() + const [selectedCategory, setSelectedCategory] = useState( + CampaignTypeCategory.medical, + ) + + const selectCategoryHandler = (category: CategoryType) => { + setSelectedCategory(category?.type) + } + return ( @@ -86,7 +95,7 @@ export default function CampaignsPage() { {t('campaigns:cta.support-cause-today')} - + ) diff --git a/src/components/client/campaigns/CustomHorizontalStepper.tsx b/src/components/client/campaigns/CustomHorizontalStepper.tsx index c06790fa6..ce4e3d7ce 100644 --- a/src/components/client/campaigns/CustomHorizontalStepper.tsx +++ b/src/components/client/campaigns/CustomHorizontalStepper.tsx @@ -8,12 +8,9 @@ type StepType = { label: 'string' component: React.ReactElement } -type StepsMap = { - [key: number]: StepType -} type Props = { - steps: StepsMap + steps: Map } export default function CustomHorizontalStepper({ steps }: Props) { @@ -30,7 +27,7 @@ export default function CustomHorizontalStepper({ steps }: Props) { * In case you need to have optional steps, * you can add their index in the optionalSteps array * **/ - const optionalSteps = [] + const optionalSteps: number[] = [] const isStepOptional = (step: number) => { return optionalSteps.includes(step) @@ -98,7 +95,7 @@ export default function CustomHorizontalStepper({ steps }: Props) { ) : ( <> - {steps.get(activeStep).component} + {steps.get(activeStep)?.component} diff --git a/src/components/client/campaigns/HSCreateCampaignPage.tsx b/src/components/client/campaigns/HSCreateCampaignPage.tsx index bb3e9843a..016a275e3 100644 --- a/src/components/client/campaigns/HSCreateCampaignPage.tsx +++ b/src/components/client/campaigns/HSCreateCampaignPage.tsx @@ -2,6 +2,7 @@ import { Box, Grid, Button } from '@mui/material' import { CategoryType } from 'gql/types' import { useTranslation } from 'next-i18next' import { useRouter } from 'next/router' +import { useState, useEffect } from 'react' import React from 'react' import Layout from 'components/client/layout/Layout' @@ -10,13 +11,18 @@ import CreateCampaignUserType from './CreateCampaignUserType' import ChevronRightIcon from '@mui/icons-material/ChevronRight' // TODO: MOBILE VIEW +// TODO add User type + +// type User = {} + export default function CreateCampaignPage() { const { t } = useTranslation('campaigns') const router = useRouter() + const [user, setUser] = useState({}) - const userTypeHandler = (user: CategoryType) => { + const userTypeHandler = (selectedUser: CategoryType) => { // TODO: store the selection on redirect - console.log('selected user type: ', user) + setUser(selectedUser) } const goToNextPage = () => router.push('/campaigns/create/steps') @@ -39,7 +45,11 @@ export default function CreateCampaignPage() { - diff --git a/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx b/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx index b46845594..c58e96079 100644 --- a/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx +++ b/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx @@ -1,9 +1,10 @@ -import { Box, Grid, Button, Typography } from '@mui/material' +import { Box } from '@mui/material' import { useTranslation } from 'next-i18next' import React from 'react' import Layout from 'components/client/layout/Layout' -import CustomHorizontalStepper from 'components/client/campaigns/CustomHorizontalStepper' +import CustomHorizontalStepper from './CustomHorizontalStepper' +import HSCreateForm from './stepOne/HSCreateForm' export default function HSCreateCampaignStepsPage() { const { t } = useTranslation() @@ -13,7 +14,7 @@ export default function HSCreateCampaignStepsPage() { 0, { label: t('campaigns:steps.step1-type'), - component: Text 1, + component: , }, ], [ diff --git a/src/components/client/campaigns/campaigns.styled.tsx b/src/components/client/campaigns/campaigns.styled.tsx index 7dd88e810..b62e47963 100644 --- a/src/components/client/campaigns/campaigns.styled.tsx +++ b/src/components/client/campaigns/campaigns.styled.tsx @@ -9,3 +9,11 @@ export const Heading = styled(Typography)(() => ({ fontFamily: 'Montserrat, sans-serif', fontSize: theme.typography.pxToRem(25), })) + +export const SectionHeading = styled(Typography)(() => ({ + fontWeight: 500, + marginBottom: theme.spacing(2), + fontFamily: 'Montserrat, sans-serif', + fontSize: theme.typography.pxToRem(35), + lineHeight: theme.typography.pxToRem(45), +})) diff --git a/src/components/client/campaigns/stepOne/BeneficiarySelect.tsx b/src/components/client/campaigns/stepOne/BeneficiarySelect.tsx new file mode 100644 index 000000000..fecfe1c35 --- /dev/null +++ b/src/components/client/campaigns/stepOne/BeneficiarySelect.tsx @@ -0,0 +1,35 @@ +import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from '@mui/material' +import { TranslatableField, translateError } from 'common/form/validation' +import { useField } from 'formik' +import { useTranslation } from 'react-i18next' +import { useBeneficiariesList } from 'service/beneficiary' + +export default function BeneficiarySelect({ name = 'beneficiaryId' }) { + const { t } = useTranslation() + const { data } = useBeneficiariesList() + const [field, meta] = useField(name) + + const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : '' + return ( + + {t('campaigns:beneficiary')} + + {helperText && {helperText}} + + ) +} diff --git a/src/components/client/campaigns/stepOne/CoordinatorSelect.tsx b/src/components/client/campaigns/stepOne/CoordinatorSelect.tsx new file mode 100644 index 000000000..f63dc8361 --- /dev/null +++ b/src/components/client/campaigns/stepOne/CoordinatorSelect.tsx @@ -0,0 +1,33 @@ +import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from '@mui/material' +import { TranslatableField, translateError } from 'common/form/validation' +import { useCoordinatorsList } from 'common/hooks/coordinators' +import { useField } from 'formik' +import { useTranslation } from 'react-i18next' + +export default function CoordinatorSelect({ name = 'coordinatorId' }) { + const { t } = useTranslation() + const { data } = useCoordinatorsList() + const [field, meta] = useField(name) + + const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : '' + return ( + + {t('campaigns:coordinator')} + + {helperText && {helperText}} + + ) +} diff --git a/src/components/client/campaigns/stepOne/HSCreateForm.tsx b/src/components/client/campaigns/stepOne/HSCreateForm.tsx new file mode 100644 index 000000000..b47adde21 --- /dev/null +++ b/src/components/client/campaigns/stepOne/HSCreateForm.tsx @@ -0,0 +1,118 @@ +import { useState } from 'react' +import { useRouter } from 'next/router' +import { useTranslation } from 'next-i18next' +import { Button, Grid, Typography } from '@mui/material' +import { routes } from 'common/routes' +import { Heading, SectionHeading } from '../campaigns.styled' +import { CampaignState } from '../../../client/campaigns/helpers/campaign.enums' + +// Validations +import * as yup from 'yup' +import { format, parse, isDate } from 'date-fns' + +// Types +import { + // CampaignResponse, + // CampaignInput, + // CampaignUploadImage, + CampaignAdminCreateFormData, +} from 'gql/campaigns' +import { Currency } from 'gql/currency' + +// Components +import ListIconButtons from '../ListIconButtons' +import Link from 'next/link' +import GenericForm from 'components/common/form/GenericForm' +import SubmitButton from 'components/common/form/SubmitButton' +import FormTextField from 'components/common/form/FormTextField' +import AcceptTermsField from 'components/common/form/AcceptTermsField' +import CampaignTypeSelect from '../CampaignTypeSelect' +import CoordinatorSelect from './CoordinatorSelect' +import BeneficiarySelect from './BeneficiarySelect' +import OrganizerSelect from './OrganizerSelect' +import CampaignFilter from '../CampaignFilter' + +// Validation helpers +const formatString = 'yyyy-MM-dd' + +const parseDateString = (value: string, originalValue: string) => { + const parsedDate = isDate(originalValue) + ? originalValue + : parse(originalValue, formatString, new Date()) + + return parsedDate +} + +// Campaigns + +export default function CampaignForm() { + const { t } = useTranslation() + + const onSubmit = () => {} + + const initialValues = {} + + // Validations + const validationSchema: yup.SchemaOf = yup + .object() + .defined() + .shape({ + title: yup.string().trim().min(10).max(200).required(), + slug: yup.string().trim().min(10).max(200).optional(), + description: yup.string().trim().min(50).max(60000).required(), + targetAmount: yup.number().integer().positive().required(), + allowDonationOnComplete: yup.bool().optional(), + campaignTypeId: yup.string().uuid().required(), + beneficiaryId: yup.string().uuid().required(), + coordinatorId: yup.string().uuid().required(), + organizerId: yup.string().uuid().required(), + startDate: yup.date().transform(parseDateString).required(), + state: yup.mixed().oneOf(Object.values(CampaignState)).required(), + endDate: yup + .date() + .transform(parseDateString) + .min(yup.ref('startDate'), `end date can't be before start date`), + terms: yup.bool().required().oneOf([true], 'validation:terms-of-use'), + gdpr: yup.bool().required().oneOf([true], 'validation:terms-of-service'), + currency: yup.mixed().oneOf(Object.values(Currency)).required(), + }) + + // type of campaign + const selectedCampaignTypeHandler = (event) => { + console.log('selected campaign: ', event) + } + return ( + + + {t('campaigns:steps.step1-type')} + + + {/* FORM */} + + + {t('campaigns:steps.step1-type')} + + + + + + {t('campaigns:campaignType')} + + + + + ) +} diff --git a/src/components/client/campaigns/stepOne/OrganizerSelect.tsx b/src/components/client/campaigns/stepOne/OrganizerSelect.tsx new file mode 100644 index 000000000..40a6503fe --- /dev/null +++ b/src/components/client/campaigns/stepOne/OrganizerSelect.tsx @@ -0,0 +1,37 @@ +import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from '@mui/material' +import { TranslatableField, translateError } from 'common/form/validation' +import { useOrganizersList } from 'common/hooks/organizer' +import { useField } from 'formik' +import { useTranslation } from 'react-i18next' + +export default function OrganizerSelect({ name = 'organizerId', label = 'campaigns:organizer' }) { + const { t } = useTranslation() + const { data } = useOrganizersList() + const [field, meta] = useField(name) + + const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : '' + if (!data) { + return null + } + + return ( + + {t(label)} + + {helperText && {helperText}} + + ) +} diff --git a/src/components/client/layout/nav/DonationMenu.tsx b/src/components/client/layout/nav/DonationMenu.tsx index 1892df88b..de68a7b1e 100644 --- a/src/components/client/layout/nav/DonationMenu.tsx +++ b/src/components/client/layout/nav/DonationMenu.tsx @@ -40,7 +40,7 @@ const allNavItems: NavItem[] = [ label: 'nav.campaigns.all-campaigns', }, { - href: routes.faq_campaigns, //temporarily lead to FAQ + href: routes.campaigns.create, //temporarily lead to FAQ label: 'nav.campaigns.create', }, ] diff --git a/src/components/common/campaign-types/categories.ts b/src/components/common/campaign-types/categories.ts index bd2597ccc..14d5fa3b2 100644 --- a/src/components/common/campaign-types/categories.ts +++ b/src/components/common/campaign-types/categories.ts @@ -10,4 +10,5 @@ export enum CampaignTypeCategory { animals = 'animals', nature = 'nature', others = 'others', + all = 'all', } diff --git a/src/gql/types.d.ts b/src/gql/types.d.ts index 5a6fca23c..470c697f8 100644 --- a/src/gql/types.d.ts +++ b/src/gql/types.d.ts @@ -1,4 +1,5 @@ import React from 'react' +import { CampaignTypeCategory } from 'components/common/campaign-types/categories' export type UUID = string @@ -15,7 +16,7 @@ export type FilterData = { export type CategoryType = { text: string - type: string + type: CampaignTypeCategory count?: number isDisabled?: boolean icon: React.ReactElement diff --git a/yarn.lock b/yarn.lock index a04a4612d..f9444be21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10892,11 +10892,11 @@ __metadata: "typescript@patch:typescript@4.8.3#~builtin": version: 4.8.3 - resolution: "typescript@patch:typescript@npm%3A4.8.3#~builtin::version=4.8.3&hash=aae4e6" + resolution: "typescript@patch:typescript@npm%3A4.8.3#~builtin::version=4.8.3&hash=3b564f" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 2222d2382fb3146089b1d27ce2b55e9d1f99cc64118f1aba75809b693b856c5d3c324f052f60c75b577947fc538bc1c27bad0eb76cbdba9a63a253489504ba7e + checksum: dfe2ee3b9da1d74b9e06784ae90c20c435db9ad6ab23172911f6cdbfd7ab7213ae3611c4254c5a2c6dc2e89f05a658b95493890bf62d218267033b3d8a2e4dd6 languageName: node linkType: hard From d4090d2c543125e0e8267cacc2d5a5ee0180acdc Mon Sep 17 00:00:00 2001 From: hstefanova Date: Thu, 27 Apr 2023 17:18:34 +0300 Subject: [PATCH 07/14] wip - create campaign layout --- .../client/campaigns/CampaignFilter.tsx | 13 ++- .../client/campaigns/CampaignsPage.tsx | 6 +- .../client/campaigns/HSCreateCampaignPage.tsx | 5 +- .../client/campaigns/ListIconButtons.tsx | 13 ++- .../client/campaigns/stepOne/HSCreateForm.tsx | 89 ++++++++++++++----- 5 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/components/client/campaigns/CampaignFilter.tsx b/src/components/client/campaigns/CampaignFilter.tsx index 113d47aa1..f42945e3b 100644 --- a/src/components/client/campaigns/CampaignFilter.tsx +++ b/src/components/client/campaigns/CampaignFilter.tsx @@ -45,9 +45,17 @@ type Props = { showCampaigns?: boolean selected: CampaignTypeCategory onClick: (item: CategoryType) => void + styles: React.CSSProperties + styleItem?: React.CSSProperties } -export default function CampaignFilter({ selected, showCampaigns = true, onClick }: Props) { +export default function CampaignFilter({ + selected, + styles, + styleItem, + showCampaigns = true, + onClick, +}: Props) { const { t } = useTranslation() const { mobile } = useMobile() const { data: campaigns, isLoading } = useCampaignList() @@ -97,7 +105,8 @@ export default function CampaignFilter({ selected, showCampaigns = true, onClick diff --git a/src/components/client/campaigns/CampaignsPage.tsx b/src/components/client/campaigns/CampaignsPage.tsx index 851a88a6c..653b620ff 100644 --- a/src/components/client/campaigns/CampaignsPage.tsx +++ b/src/components/client/campaigns/CampaignsPage.tsx @@ -95,7 +95,11 @@ export default function CampaignsPage() { {t('campaigns:cta.support-cause-today')} - +
) diff --git a/src/components/client/campaigns/HSCreateCampaignPage.tsx b/src/components/client/campaigns/HSCreateCampaignPage.tsx index 016a275e3..333c3133b 100644 --- a/src/components/client/campaigns/HSCreateCampaignPage.tsx +++ b/src/components/client/campaigns/HSCreateCampaignPage.tsx @@ -18,11 +18,12 @@ import ChevronRightIcon from '@mui/icons-material/ChevronRight' export default function CreateCampaignPage() { const { t } = useTranslation('campaigns') const router = useRouter() - const [user, setUser] = useState({}) + // const [user, setUser] = useState({}) + const [user, setUser] = useState(null) const userTypeHandler = (selectedUser: CategoryType) => { // TODO: store the selection on redirect - setUser(selectedUser) + setUser(selectedUser) //it shouldn't be null } const goToNextPage = () => router.push('/campaigns/create/steps') diff --git a/src/components/client/campaigns/ListIconButtons.tsx b/src/components/client/campaigns/ListIconButtons.tsx index c4e323d54..40a269877 100644 --- a/src/components/client/campaigns/ListIconButtons.tsx +++ b/src/components/client/campaigns/ListIconButtons.tsx @@ -48,9 +48,18 @@ type Props = { rowHeight?: number gap?: number style?: React.CSSProperties + styleItem?: React.CSSProperties } -export default function ListIconButtons({ data, onClick, cols, rowHeight, gap, style }: Props) { +export default function ListIconButtons({ + data, + onClick, + cols, + rowHeight, + gap, + style, + styleItem = { display: 'inline', margin: '0 auto' }, +}: Props) { return ( @@ -58,7 +67,7 @@ export default function ListIconButtons({ data, onClick, cols, rowHeight, gap, s const hasCountProperty = Object.keys(item).includes('count') return ( - + = yup .object() .defined() @@ -78,9 +82,21 @@ export default function CampaignForm() { }) // type of campaign - const selectedCampaignTypeHandler = (event) => { - console.log('selected campaign: ', event) + const [selectedCampaignType, setSelectedCampaignType] = useState( + CampaignTypeCategory.all, + ) + const selectedCampaignTypeHandler = (type: CategoryType) => { + setSelectedCampaignType(type) } + + const beneficiaryTypes = [ + { value: 'personal', label: 'Кампанията е лична' }, + { + value: 'beneficiary', + label: 'Кампанията се организира за друг бенефициент - физическо лице', + }, + ] + return ( @@ -92,25 +108,58 @@ export default function CampaignForm() { onSubmit={onSubmit} initialValues={initialValues} validationSchema={validationSchema}> - - {t('campaigns:steps.step1-type')} - - - + + {/* Campaign Name */} + + + {t('campaigns:steps.step1-type')} + + + + {/* Campaign Type */} + + {t('campaigns:campaignType')} + + + {/* Campaign Beneficiary */} + + Бенфициент на кампанията + + + + + + + + + {/* Amount */} - - {t('campaigns:campaignType')} - + + {t('campaigns:campaign.amount')} + + + + From 4bc07f4f6a39bc726e525ad8d294aa4af4cd3024 Mon Sep 17 00:00:00 2001 From: hstefanova Date: Thu, 4 May 2023 12:09:36 +0300 Subject: [PATCH 08/14] updates on the stepper approach - it is changed to use React context --- src/common/routes.ts | 1 + .../campaigns/CustomHorizontalStepper.tsx | 127 +++------ .../campaigns/HSCreateCampaignStepsPage.tsx | 47 +--- .../client/campaigns/stepOne/HSCreateForm.tsx | 114 +++++++- src/context/create-campaign..tsx | 259 ++++++++++++++++++ yarn.lock | 4 +- 6 files changed, 402 insertions(+), 150 deletions(-) create mode 100644 src/context/create-campaign..tsx diff --git a/src/common/routes.ts b/src/common/routes.ts index b7708ce30..a2a951eca 100644 --- a/src/common/routes.ts +++ b/src/common/routes.ts @@ -78,6 +78,7 @@ export const routes = { campaigns: { index: '/campaigns', create: '/campaigns/create', + steps: '/campaigns/create/steps', viewCampaignBySlug: (slug: string) => `/campaigns/${slug}`, viewExpenses: (slug: string) => `/campaigns/${slug}/expenses`, oneTimeDonation: (slug: string) => `/campaigns/donation/${slug}`, diff --git a/src/components/client/campaigns/CustomHorizontalStepper.tsx b/src/components/client/campaigns/CustomHorizontalStepper.tsx index ce4e3d7ce..a88d41a50 100644 --- a/src/components/client/campaigns/CustomHorizontalStepper.tsx +++ b/src/components/client/campaigns/CustomHorizontalStepper.tsx @@ -1,125 +1,60 @@ -import React from 'react' -import ChevronRightIcon from '@mui/icons-material/ChevronRight' +import React, { useContext } from 'react' +import { Box, Grid, Button, Stepper, Step, StepLabel, Typography } from '@mui/material' +import { CampaignContext } from 'context/create-campaign.' -import { Box, Grid, Stepper, Step, StepLabel, Button, Typography } from '@mui/material' -import { useTranslation } from 'next-i18next' - -type StepType = { - label: 'string' - component: React.ReactElement -} - -type Props = { - steps: Map -} - -export default function CustomHorizontalStepper({ steps }: Props) { - const [activeStep, setActiveStep] = React.useState(0) - const [skipped, setSkipped] = React.useState(new Set()) - const { t } = useTranslation('campaigns') - - const labels = [] - for (const step of steps.values()) { - labels.push(step.label) - } - - /*** - * In case you need to have optional steps, - * you can add their index in the optionalSteps array - * **/ - const optionalSteps: number[] = [] - - const isStepOptional = (step: number) => { - return optionalSteps.includes(step) - } - - const isStepSkipped = (step: number) => { - return skipped.has(step) - } - - const handleNext = () => { - let newSkipped = skipped - if (isStepSkipped(activeStep)) { - newSkipped = new Set(newSkipped.values()) - newSkipped.delete(activeStep) - } - - setActiveStep((prevActiveStep) => prevActiveStep + 1) - setSkipped(newSkipped) - } - - const handleBack = () => { - setActiveStep((prevActiveStep) => prevActiveStep - 1) - } - - const handleSkip = () => { - if (!isStepOptional(activeStep)) { - // You probably want to guard against something like this, - // it should never occur unless someone's actively trying to break something. - throw new Error("You can't skip a step that isn't optional.") - } - - setActiveStep((prevActiveStep) => prevActiveStep + 1) - setSkipped((prevSkipped) => { - const newSkipped = new Set(prevSkipped.values()) - newSkipped.add(activeStep) - return newSkipped - }) - } +import HSCreateForm from 'components/client/campaigns/stepOne/HSCreateForm' +// TODO: Fix the Typescript errors +export default function CustomHorizontalStepper() { + const ctx = useContext(CampaignContext) return ( - - {labels.map((label, index) => { + + {ctx.steps.map((step, index) => { const stepProps: { completed?: boolean } = {} const labelProps: { optional?: React.ReactNode } = {} - if (isStepOptional(index)) { + if (ctx.isStepOptional(index)) { labelProps.optional = Optional } - if (isStepSkipped(index)) { + if (ctx.isStepSkipped(index)) { stepProps.completed = false } return ( - {label} + {step.title} ) })} - {activeStep === steps.size ? ( + {ctx.activeStep === ctx.steps.length ? ( <> All steps completed - you're finished ) : ( <> - {steps.get(activeStep)?.component} - - - - - - - - - {isStepOptional(activeStep) && ( - - )} - + {ctx.activeStep === 0 && } + {ctx.activeStep === 1 && Page {ctx.activeStep}} + {ctx.activeStep === 2 && Page {ctx.activeStep}} + {ctx.activeStep === 3 && Page {ctx.activeStep}} + {ctx.activeStep === 4 && Page {ctx.activeStep}} + + {/* Action Buttons */} + + + + - - - + + - + )} diff --git a/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx b/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx index c58e96079..6970061fd 100644 --- a/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx +++ b/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx @@ -6,51 +6,16 @@ import Layout from 'components/client/layout/Layout' import CustomHorizontalStepper from './CustomHorizontalStepper' import HSCreateForm from './stepOne/HSCreateForm' -export default function HSCreateCampaignStepsPage() { - const { t } = useTranslation() - - const steps = new Map([ - [ - 0, - { - label: t('campaigns:steps.step1-type'), - component: , - }, - ], - [ - 1, - { - label: t('campaigns:steps.step2-type'), - component: Text 2, - }, - ], - [ - 2, - { - label: t('campaigns:steps.step3-type'), - component: Text 3, - }, - ], - [ - 3, - { - label: t('campaigns:steps.step4-type'), - component: Text 4, - }, - ], - [ - 4, - { - label: t('campaigns:steps.step4-type'), - component: Text 5, - }, - ], - ]) +import { CampaignContext, CampaignProvider } from 'context/create-campaign.' +import CampaignStepper from './CampaignStepper' +export default function HSCreateCampaignStepsPage() { return ( - + + + ) diff --git a/src/components/client/campaigns/stepOne/HSCreateForm.tsx b/src/components/client/campaigns/stepOne/HSCreateForm.tsx index dc9caf728..c98fbefc7 100644 --- a/src/components/client/campaigns/stepOne/HSCreateForm.tsx +++ b/src/components/client/campaigns/stepOne/HSCreateForm.tsx @@ -1,11 +1,29 @@ -import { useState } from 'react' +/** + * TODO: + * - fix the typescript errors on the functions + * - fix the beneficiary data in the context + * - fix the validations + * - store the data in localStorage in case the user refreshes the page + */ + +import { useState, useContext } from 'react' import { useRouter } from 'next/router' import { useTranslation } from 'next-i18next' -import { Button, Grid, Typography } from '@mui/material' +import { + Button, + Grid, + Typography, + FormControl, + RadioGroup, + FormLabel, + FormControlLabel, + Radio, +} from '@mui/material' import { routes } from 'common/routes' import { Heading, SectionHeading } from '../campaigns.styled' import { CampaignState } from '../../../client/campaigns/helpers/campaign.enums' import RadioButtonGroup from 'components/common/form/RadioButtonGroup' +import ChevronRightIcon from '@mui/icons-material/ChevronRight' // Validations import * as yup from 'yup' @@ -35,6 +53,8 @@ import BeneficiarySelect from './BeneficiarySelect' import OrganizerSelect from './OrganizerSelect' import CampaignFilter from '../CampaignFilter' +import { CampaignContext } from 'context/create-campaign.' + // Validation helpers const formatString = 'yyyy-MM-dd' @@ -51,6 +71,9 @@ const parseDateString = (value: string, originalValue: string) => { export default function CampaignForm() { const { t } = useTranslation() + const ctx = useContext(CampaignContext) + const campaignInfo = ctx.campaignData.info + const onSubmit = () => {} const initialValues = {} @@ -89,14 +112,6 @@ export default function CampaignForm() { setSelectedCampaignType(type) } - const beneficiaryTypes = [ - { value: 'personal', label: 'Кампанията е лична' }, - { - value: 'beneficiary', - label: 'Кампанията се организира за друг бенефициент - физическо лице', - }, - ] - return ( @@ -119,6 +134,8 @@ export default function CampaignForm() { name="title" label="campaigns:campaign.title" autoComplete="title" + onChange={ctx.setCampaignInfo('name')} + value={campaignInfo.name} /> {/* Campaign Type */} @@ -128,6 +145,7 @@ export default function CampaignForm() { selected={selectedCampaignType} showCampaigns={false} onClick={selectedCampaignTypeHandler} + onChange={ctx.setCampaignInfo('category')} styles={{ maxWidth: 'md', margin: 'left' }} /> @@ -135,7 +153,19 @@ export default function CampaignForm() { Бенфициент на кампанията - + + } label="Кампанията е лична" /> + } + label="Кампанията се организира за друг бенефициент - физическо лице" + /> + @@ -144,6 +174,8 @@ export default function CampaignForm() { label="Три имена на бенефициент" name="beneficiary" autoComplete="beneficiary" + value={campaignInfo.beneficiary.name} + onChange={ctx.setCampaignInfo('beneficiary')} /> @@ -156,10 +188,70 @@ export default function CampaignForm() { + + {/* End of the campaign */} + + + Желана крайна дата на кампанията: + + + } + label="До събиране на необходимите средства" + /> + } + label="Ежемесечна кампания" + /> + } label="До дата" /> + + + + {/* TODO: Show this field if 'specific-date' is selected */} + + + + {/* Action Buttons */} + + + + + + + + + diff --git a/src/context/create-campaign..tsx b/src/context/create-campaign..tsx new file mode 100644 index 000000000..8b52c990b --- /dev/null +++ b/src/context/create-campaign..tsx @@ -0,0 +1,259 @@ +import React, { useState, createContext } from 'react' +import { useTranslation } from 'react-i18next' + +import type { CampaignTypeCategory } from 'components/common/campaign-types/categories' +import { UUID } from 'gql/types' +import { BeneficiaryType } from '../components/admin/beneficiary/BeneficiaryTypes' +import { CampaignFile } from 'gql/campaigns' + +// TODO: Extract the different types +type CampaignDataType = { + info: { + name: string + category: CampaignTypeCategory | '' + beneficiary: { + id: UUID + type: BeneficiaryType + name: string + } + targetAmount: number + // endDate?: Date + endDate?: 'one-time' | 'each-month' | 'specific-date' //temporary + } + organizer: { + person: { + id: UUID + firstName: string + middleName: string + lastName: string + pin: string + phoneNumber: string + } + company: { + id: UUID + name: string + address: string + bulstatUIC: string + phoneNumber: string + representative: { + firstName: string + middleName: string + lastName: string + } + } + } + about: { + organizer: string + campaign: string + completed: string + personalStories: string + guarantor?: { + name: string + profession: string + avatar?: string // not sure + info: string + } + webpage: string + links: string + facebook: string + } + documents: { + video: string //not sure + pictures: string //not sure + files: CampaignFile[] + } +} + +type Props = { + children: React.ReactNode +} + +type Label = { title: string } + +type StepsCampaignContext = { + activeStep: number + steps: Label[] + nextPage?: (event: React.MouseEvent) => void + prevPage?: (event: React.MouseEvent) => void + campaignData: CampaignDataType + isStepOptional?: (step: number) => boolean + isStepSkipped?: (step: number) => boolean + handleChange?: (event: React.MouseEvent) => void + setCampaignInfo?: (event: React.MouseEvent) => void + setOrganizer?: (event: React.MouseEvent) => void + setAboutCampaign?: (event: React.MouseEvent) => void + setDocuments?: (event: React.MouseEvent) => void +} + +export const CampaignContext = createContext({} as StepsCampaignContext) + +export const CampaignProvider = (props: Props) => { + const { t } = useTranslation() + const [campaignData, setCampaignData] = useState({ + info: { + name: '', + category: 'all', + beneficiary: { + id: '', + type: '', + name: '', + }, + targetAmount: 0, + endDate: new Date(), + }, + organizer: { + person: { + id: '', + firstName: '', + middleName: '', + lastName: '', + pin: '', + phoneNumber: '', + }, + campany: { + id: '', + name: '', + address: '', + bulstatUIC: '', + phoneNumber: '', + representative: { + firstName: '', + middleName: '', + lastName: '', + }, + }, + }, + about: { + organizer: '', + campaign: '', + completed: '', + personalStories: '', + guarantor: { + name: '', + profession: '', + avatar: '', + info: '', + }, + webpage: '', + links: '', + facebook: '', + }, + documents: { + video: '', + pictures: '', + files: '', + }, + }) + + const [activeStep, setActiveStep] = useState(0) + const [skipped, setSkipped] = useState(new Set()) + + const steps = [ + { title: t('campaigns:steps.step1-type') }, + { title: t('campaigns:steps.step2-type') }, + { title: t('campaigns:steps.step3-type') }, + { title: t('campaigns:steps.step4-type') }, + { title: t('campaigns:steps.step5-type') }, + ] + + /** + * Optional Steps / Skip + **/ + const optionalSteps: number[] = [] + + const isStepOptional = (step: number) => { + return optionalSteps.includes(step) + } + + const isStepSkipped = (step: number) => { + return skipped.has(step) + } + + const handleSkip = () => { + if (!isStepOptional(activeStep)) { + throw new Error("You can't skip a step that isn't optional.") + } + + setActiveStep((prevActiveStep) => prevActiveStep + 1) + + setSkipped((prevSkipped) => { + const newSkipped = new Set(prevSkipped.values()) + newSkipped.add(activeStep) + return newSkipped + }) + } + + /** + * Prev / Next Actions + **/ + const nextPage = () => { + let newSkipped = skipped + + if (isStepSkipped(activeStep)) { + newSkipped = new Set(newSkipped.values()) + newSkipped.delete(activeStep) + } + + setActiveStep((prevActiveStep) => prevActiveStep + 1) + setSkipped(newSkipped) + + console.log('next page: ', campaignData.info) + } + + const prevPage = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1) + } + + /** + * Campaign Data Actions + **/ + const handleChange = (prop: string) => (event: React.ChangeEvent) => { + setCampaignData({ ...campaignData, [prop]: event.target.value }) + } + + const setCampaignInfo = (prop: string) => (event: React.ChangeEvent) => { + setCampaignData({ + ...campaignData, + info: { ...campaignData.info, [prop]: event.target.value }, + }) + } + const setOrganizer = (prop: string) => (event: React.ChangeEvent) => { + setCampaignData({ + ...campaignData, + organizer: { ...campaignData.organizer, [prop]: event.target.value }, + }) + } + const setAboutCampaign = (prop: string) => (event: React.ChangeEvent) => { + setCampaignData({ + ...campaignData, + about: { ...campaignData.about, [prop]: event.target.value }, + }) + } + const setDocuments = (prop: string) => (event: React.ChangeEvent) => { + setCampaignData({ + ...campaignData, + documents: { ...campaignData.documents, [prop]: event.target.value }, + }) + } + + return ( + + {props.children} + + ) +} diff --git a/yarn.lock b/yarn.lock index f9444be21..a04a4612d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10892,11 +10892,11 @@ __metadata: "typescript@patch:typescript@4.8.3#~builtin": version: 4.8.3 - resolution: "typescript@patch:typescript@npm%3A4.8.3#~builtin::version=4.8.3&hash=3b564f" + resolution: "typescript@patch:typescript@npm%3A4.8.3#~builtin::version=4.8.3&hash=aae4e6" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: dfe2ee3b9da1d74b9e06784ae90c20c435db9ad6ab23172911f6cdbfd7ab7213ae3611c4254c5a2c6dc2e89f05a658b95493890bf62d218267033b3d8a2e4dd6 + checksum: 2222d2382fb3146089b1d27ce2b55e9d1f99cc64118f1aba75809b693b856c5d3c324f052f60c75b577947fc538bc1c27bad0eb76cbdba9a63a253489504ba7e languageName: node linkType: hard From dbf7418e98ae2b050733bc69de52d74018aee716 Mon Sep 17 00:00:00 2001 From: mm-hstefanova Date: Thu, 11 May 2023 09:49:01 +0300 Subject: [PATCH 09/14] rollback the create campaign route --- src/components/client/campaigns/CustomHorizontalStepper.tsx | 4 ++-- src/components/client/layout/nav/DonationMenu.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/client/campaigns/CustomHorizontalStepper.tsx b/src/components/client/campaigns/CustomHorizontalStepper.tsx index a88d41a50..032a7ff03 100644 --- a/src/components/client/campaigns/CustomHorizontalStepper.tsx +++ b/src/components/client/campaigns/CustomHorizontalStepper.tsx @@ -42,7 +42,7 @@ export default function CustomHorizontalStepper() { {ctx.activeStep === 4 && Page {ctx.activeStep}} {/* Action Buttons */} - + {/* - + */} )} diff --git a/src/components/client/layout/nav/DonationMenu.tsx b/src/components/client/layout/nav/DonationMenu.tsx index de68a7b1e..1892df88b 100644 --- a/src/components/client/layout/nav/DonationMenu.tsx +++ b/src/components/client/layout/nav/DonationMenu.tsx @@ -40,7 +40,7 @@ const allNavItems: NavItem[] = [ label: 'nav.campaigns.all-campaigns', }, { - href: routes.campaigns.create, //temporarily lead to FAQ + href: routes.faq_campaigns, //temporarily lead to FAQ label: 'nav.campaigns.create', }, ] From c7d905109c572a71f95932c70fd48fafb04817fa Mon Sep 17 00:00:00 2001 From: mm-hstefanova Date: Thu, 11 May 2023 10:24:00 +0300 Subject: [PATCH 10/14] fix typo --- src/components/client/campaigns/CustomHorizontalStepper.tsx | 2 +- src/components/client/campaigns/HSCreateCampaignStepsPage.tsx | 2 +- src/components/client/campaigns/stepOne/HSCreateForm.tsx | 2 +- src/components/client/layout/nav/DonationMenu.tsx | 3 ++- src/context/{create-campaign..tsx => create-campaign.tsx} | 0 5 files changed, 5 insertions(+), 4 deletions(-) rename src/context/{create-campaign..tsx => create-campaign.tsx} (100%) diff --git a/src/components/client/campaigns/CustomHorizontalStepper.tsx b/src/components/client/campaigns/CustomHorizontalStepper.tsx index 032a7ff03..26c3b09ce 100644 --- a/src/components/client/campaigns/CustomHorizontalStepper.tsx +++ b/src/components/client/campaigns/CustomHorizontalStepper.tsx @@ -1,6 +1,6 @@ import React, { useContext } from 'react' import { Box, Grid, Button, Stepper, Step, StepLabel, Typography } from '@mui/material' -import { CampaignContext } from 'context/create-campaign.' +import { CampaignContext } from 'context/create-campaign' import HSCreateForm from 'components/client/campaigns/stepOne/HSCreateForm' // TODO: Fix the Typescript errors diff --git a/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx b/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx index 6970061fd..1f151e31a 100644 --- a/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx +++ b/src/components/client/campaigns/HSCreateCampaignStepsPage.tsx @@ -6,7 +6,7 @@ import Layout from 'components/client/layout/Layout' import CustomHorizontalStepper from './CustomHorizontalStepper' import HSCreateForm from './stepOne/HSCreateForm' -import { CampaignContext, CampaignProvider } from 'context/create-campaign.' +import { CampaignContext, CampaignProvider } from 'context/create-campaign' import CampaignStepper from './CampaignStepper' export default function HSCreateCampaignStepsPage() { diff --git a/src/components/client/campaigns/stepOne/HSCreateForm.tsx b/src/components/client/campaigns/stepOne/HSCreateForm.tsx index c98fbefc7..1f1582821 100644 --- a/src/components/client/campaigns/stepOne/HSCreateForm.tsx +++ b/src/components/client/campaigns/stepOne/HSCreateForm.tsx @@ -53,7 +53,7 @@ import BeneficiarySelect from './BeneficiarySelect' import OrganizerSelect from './OrganizerSelect' import CampaignFilter from '../CampaignFilter' -import { CampaignContext } from 'context/create-campaign.' +import { CampaignContext } from 'context/create-campaign' // Validation helpers const formatString = 'yyyy-MM-dd' diff --git a/src/components/client/layout/nav/DonationMenu.tsx b/src/components/client/layout/nav/DonationMenu.tsx index 1892df88b..2831155b9 100644 --- a/src/components/client/layout/nav/DonationMenu.tsx +++ b/src/components/client/layout/nav/DonationMenu.tsx @@ -40,7 +40,8 @@ const allNavItems: NavItem[] = [ label: 'nav.campaigns.all-campaigns', }, { - href: routes.faq_campaigns, //temporarily lead to FAQ + href: routes.campaigns.create, //temporarily lead to FAQ + label: 'nav.campaigns.create', }, ] diff --git a/src/context/create-campaign..tsx b/src/context/create-campaign.tsx similarity index 100% rename from src/context/create-campaign..tsx rename to src/context/create-campaign.tsx From 8f078b0854545c48cb2c5204b27e25f2a14178e8 Mon Sep 17 00:00:00 2001 From: ani-kalpachka Date: Fri, 2 Jun 2023 18:20:39 +0300 Subject: [PATCH 11/14] fix lint errors --- .../client/campaigns/stepOne/HSCreateForm.tsx | 28 ++++--------------- .../client/one-time-donation/Steps.tsx | 4 --- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/src/components/client/campaigns/stepOne/HSCreateForm.tsx b/src/components/client/campaigns/stepOne/HSCreateForm.tsx index 1f1582821..7221c3b17 100644 --- a/src/components/client/campaigns/stepOne/HSCreateForm.tsx +++ b/src/components/client/campaigns/stepOne/HSCreateForm.tsx @@ -7,27 +7,15 @@ */ import { useState, useContext } from 'react' -import { useRouter } from 'next/router' import { useTranslation } from 'next-i18next' -import { - Button, - Grid, - Typography, - FormControl, - RadioGroup, - FormLabel, - FormControlLabel, - Radio, -} from '@mui/material' -import { routes } from 'common/routes' +import { Button, Grid, FormControl, RadioGroup, FormControlLabel, Radio } from '@mui/material' import { Heading, SectionHeading } from '../campaigns.styled' import { CampaignState } from '../../../client/campaigns/helpers/campaign.enums' -import RadioButtonGroup from 'components/common/form/RadioButtonGroup' import ChevronRightIcon from '@mui/icons-material/ChevronRight' // Validations import * as yup from 'yup' -import { format, parse, isDate } from 'date-fns' +import { parse, isDate } from 'date-fns' // Types import { @@ -41,16 +29,8 @@ import { CategoryType } from 'gql/types' import { CampaignTypeCategory } from 'components/common/campaign-types/categories' // Components -import ListIconButtons from '../ListIconButtons' -import Link from 'next/link' import GenericForm from 'components/common/form/GenericForm' -import SubmitButton from 'components/common/form/SubmitButton' import FormTextField from 'components/common/form/FormTextField' -import AcceptTermsField from 'components/common/form/AcceptTermsField' -import CampaignTypeSelect from '../CampaignTypeSelect' -import CoordinatorSelect from './CoordinatorSelect' -import BeneficiarySelect from './BeneficiarySelect' -import OrganizerSelect from './OrganizerSelect' import CampaignFilter from '../CampaignFilter' import { CampaignContext } from 'context/create-campaign' @@ -74,7 +54,9 @@ export default function CampaignForm() { const ctx = useContext(CampaignContext) const campaignInfo = ctx.campaignData.info - const onSubmit = () => {} + const onSubmit = () => { + console.log('Form submitted') + } const initialValues = {} diff --git a/src/components/client/one-time-donation/Steps.tsx b/src/components/client/one-time-donation/Steps.tsx index 1b154e0a2..8cb7ff634 100644 --- a/src/components/client/one-time-donation/Steps.tsx +++ b/src/components/client/one-time-donation/Steps.tsx @@ -63,10 +63,6 @@ export default function DonationStepper({ onStepChange }: DonationStepperProps) if (isLoading || !data) return const { campaign } = data - function isLogged() { - return session && session.accessToken ? true : false - } - initialValues.isRecurring = false const userEmail = session?.user?.email From f55da7267c68a7dd4ac7af6941c0622e7089b756 Mon Sep 17 00:00:00 2001 From: ani-kalpachka Date: Fri, 2 Jun 2023 18:44:37 +0300 Subject: [PATCH 12/14] fix types --- src/gql/types.d.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/gql/types.d.ts b/src/gql/types.d.ts index 4e666fb89..4cd2270da 100644 --- a/src/gql/types.d.ts +++ b/src/gql/types.d.ts @@ -1,6 +1,3 @@ -import React from 'react' -import { CampaignTypeCategory } from 'components/common/campaign-types/categories' - export type UUID = string export type PaginationData = { @@ -19,8 +16,8 @@ export type FilterData = { export type CategoryType = { text: string - type: CampaignTypeCategory + type: string count?: number isDisabled?: boolean - icon: React.ReactElement + icon: ReactElement } From 8cda195a0d08ed0c187938173c3c3b99f4d4d0df Mon Sep 17 00:00:00 2001 From: ani-kalpachka Date: Fri, 2 Jun 2023 18:56:40 +0300 Subject: [PATCH 13/14] fix types --- src/gql/types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gql/types.d.ts b/src/gql/types.d.ts index 4cd2270da..c56a2b9eb 100644 --- a/src/gql/types.d.ts +++ b/src/gql/types.d.ts @@ -16,7 +16,7 @@ export type FilterData = { export type CategoryType = { text: string - type: string + type: CampaignTypeCategory count?: number isDisabled?: boolean icon: ReactElement From 2740d9911231e34fd59b28aa28f0f1277ff98de7 Mon Sep 17 00:00:00 2001 From: ani-kalpachka Date: Fri, 2 Jun 2023 19:19:53 +0300 Subject: [PATCH 14/14] Add checks --- src/components/client/campaigns/CustomHorizontalStepper.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/client/campaigns/CustomHorizontalStepper.tsx b/src/components/client/campaigns/CustomHorizontalStepper.tsx index 26c3b09ce..af473be10 100644 --- a/src/components/client/campaigns/CustomHorizontalStepper.tsx +++ b/src/components/client/campaigns/CustomHorizontalStepper.tsx @@ -15,10 +15,10 @@ export default function CustomHorizontalStepper() { const labelProps: { optional?: React.ReactNode } = {} - if (ctx.isStepOptional(index)) { + if (ctx.isStepOptional && ctx.isStepOptional(index)) { labelProps.optional = Optional } - if (ctx.isStepSkipped(index)) { + if (ctx.isStepSkipped && ctx.isStepSkipped(index)) { stepProps.completed = false } return (