From 37c6b318a6ce436f2f395df6716a250405f9fded Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Thu, 14 Aug 2025 06:26:34 +0300 Subject: [PATCH] [frontend]: add sponsorship pages --- .../src/app/(site)/dashboard/plans/page.tsx | 20 ++ .../app/(site)/dashboard/sponsors/page.tsx | 20 ++ .../(site)/dashboard/transactions/page.tsx | 20 ++ frontend/src/app/account/deactivate/page.tsx | 5 +- .../features/auth/forms/DeactivateForm.tsx | 2 +- .../chat/settings/ChangeChatSettings.tsx | 2 +- .../sponsorship/plan/forms/CreatePlanForm.tsx | 174 ++++++++++++ .../sponsorship/plan/table/PlansTable.tsx | 135 +++++++++ .../plan/table/VerifiedChannelAlert.tsx | 20 ++ .../subscription/table/SponsorsTable.tsx | 112 ++++++++ .../transactions/table/TransactionsTable.tsx | 115 ++++++++ .../user/profile/ChangeAvatarForm.tsx | 1 - frontend/src/graphql/generated/output.ts | 265 ++++++++++++++++++ .../plan/CreateSponsorshipPlan.graphql | 5 + .../plan/RemoveSponsorshipPlan.graphql | 5 + .../transaction/MakePayment.graphql | 5 + .../plan/FindMySponsorshipPlans.graphql | 8 + .../subsciption/FindMySponsors.graphql | 13 + .../transaction/FindMyTransactions.graphql | 7 + frontend/src/libs/apollo-client.ts | 6 +- frontend/src/libs/constants/url.constants.ts | 2 +- .../src/schemas/plan/create-plan.schema.ts | 9 + frontend/src/utils/convert-price.ts | 6 + 23 files changed, 948 insertions(+), 9 deletions(-) create mode 100644 frontend/src/app/(site)/dashboard/plans/page.tsx create mode 100644 frontend/src/app/(site)/dashboard/sponsors/page.tsx create mode 100644 frontend/src/app/(site)/dashboard/transactions/page.tsx create mode 100644 frontend/src/components/features/sponsorship/plan/forms/CreatePlanForm.tsx create mode 100644 frontend/src/components/features/sponsorship/plan/table/PlansTable.tsx create mode 100644 frontend/src/components/features/sponsorship/plan/table/VerifiedChannelAlert.tsx create mode 100644 frontend/src/components/features/sponsorship/subscription/table/SponsorsTable.tsx create mode 100644 frontend/src/components/features/sponsorship/transactions/table/TransactionsTable.tsx create mode 100644 frontend/src/graphql/mutations/sponsorship/plan/CreateSponsorshipPlan.graphql create mode 100644 frontend/src/graphql/mutations/sponsorship/plan/RemoveSponsorshipPlan.graphql create mode 100644 frontend/src/graphql/mutations/sponsorship/transaction/MakePayment.graphql create mode 100644 frontend/src/graphql/queries/sponsorship/plan/FindMySponsorshipPlans.graphql create mode 100644 frontend/src/graphql/queries/sponsorship/subsciption/FindMySponsors.graphql create mode 100644 frontend/src/graphql/queries/sponsorship/transaction/FindMyTransactions.graphql create mode 100644 frontend/src/schemas/plan/create-plan.schema.ts create mode 100644 frontend/src/utils/convert-price.ts diff --git a/frontend/src/app/(site)/dashboard/plans/page.tsx b/frontend/src/app/(site)/dashboard/plans/page.tsx new file mode 100644 index 0000000..14c7deb --- /dev/null +++ b/frontend/src/app/(site)/dashboard/plans/page.tsx @@ -0,0 +1,20 @@ +import { getTranslations } from 'next-intl/server'; + +import { PlansTable } from '@/components/features/sponsorship/plan/table/PlansTable'; +import { NO_INDEX_PAGE } from '@/libs/constants/seo.constants'; + +import type { Metadata } from 'next'; + +export async function generateMetadata(): Promise { + const t = await getTranslations('dashboard.plans.header'); + + return { + title: t('heading'), + description: t('description'), + ...NO_INDEX_PAGE, + }; +} + +const PlansPage = () => ; + +export default PlansPage; diff --git a/frontend/src/app/(site)/dashboard/sponsors/page.tsx b/frontend/src/app/(site)/dashboard/sponsors/page.tsx new file mode 100644 index 0000000..500447e --- /dev/null +++ b/frontend/src/app/(site)/dashboard/sponsors/page.tsx @@ -0,0 +1,20 @@ +import { getTranslations } from 'next-intl/server'; + +import { SponsorsTable } from '@/components/features/sponsorship/subscription/table/SponsorsTable'; +import { NO_INDEX_PAGE } from '@/libs/constants/seo.constants'; + +import type { Metadata } from 'next'; + +export async function generateMetadata(): Promise { + const t = await getTranslations('dashboard.sponsors.header'); + + return { + title: t('heading'), + description: t('description'), + ...NO_INDEX_PAGE, + }; +} + +const SponsorsPage = () => ; + +export default SponsorsPage; diff --git a/frontend/src/app/(site)/dashboard/transactions/page.tsx b/frontend/src/app/(site)/dashboard/transactions/page.tsx new file mode 100644 index 0000000..13dac3c --- /dev/null +++ b/frontend/src/app/(site)/dashboard/transactions/page.tsx @@ -0,0 +1,20 @@ +import { getTranslations } from 'next-intl/server'; + +import { TransactionsTable } from '@/components/features/sponsorship/transactions/table/TransactionsTable'; +import { NO_INDEX_PAGE } from '@/libs/constants/seo.constants'; + +import type { Metadata } from 'next'; + +export async function generateMetadata(): Promise { + const t = await getTranslations('dashboard.transactions.header'); + + return { + title: t('heading'), + description: t('description'), + ...NO_INDEX_PAGE, + }; +} + +const TransactionsPage = () => ; + +export default TransactionsPage; diff --git a/frontend/src/app/account/deactivate/page.tsx b/frontend/src/app/account/deactivate/page.tsx index 683d213..344d949 100644 --- a/frontend/src/app/account/deactivate/page.tsx +++ b/frontend/src/app/account/deactivate/page.tsx @@ -14,6 +14,5 @@ export async function generateMetadata(): Promise { }; } -export default function DeactivatePage() { - return ; -} +const DeactivatePage = () => ; +export default DeactivatePage; diff --git a/frontend/src/components/features/auth/forms/DeactivateForm.tsx b/frontend/src/components/features/auth/forms/DeactivateForm.tsx index 3723d64..57d317b 100644 --- a/frontend/src/components/features/auth/forms/DeactivateForm.tsx +++ b/frontend/src/components/features/auth/forms/DeactivateForm.tsx @@ -63,7 +63,7 @@ export const DeactivateForm = () => { const { isValid } = form.formState; function onSubmit(data: TypeDeactivateSchema) { - deactivate({ variables: { data } }); + void deactivate({ variables: { data } }); } return ( diff --git a/frontend/src/components/features/chat/settings/ChangeChatSettings.tsx b/frontend/src/components/features/chat/settings/ChangeChatSettings.tsx index 6f824a0..0b9cdf3 100644 --- a/frontend/src/components/features/chat/settings/ChangeChatSettings.tsx +++ b/frontend/src/components/features/chat/settings/ChangeChatSettings.tsx @@ -47,7 +47,7 @@ export const ChangeChatSettings = () => { ) { form.setValue(field, value); - update({ + void update({ variables: { data: { ...form.getValues(), [field]: value }, }, diff --git a/frontend/src/components/features/sponsorship/plan/forms/CreatePlanForm.tsx b/frontend/src/components/features/sponsorship/plan/forms/CreatePlanForm.tsx new file mode 100644 index 0000000..eabe8ff --- /dev/null +++ b/frontend/src/components/features/sponsorship/plan/forms/CreatePlanForm.tsx @@ -0,0 +1,174 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useTranslations } from 'next-intl'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { toast } from 'sonner'; + +import { Button } from '@/components/ui/common/Button'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/common/Dialog'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, +} from '@/components/ui/common/Form'; +import { Input } from '@/components/ui/common/Input'; +import { Textarea } from '@/components/ui/common/Textarea'; +import { + useCreateSponsorshipPlanMutation, + useFindMySponsorshipPlansQuery, +} from '@/graphql/generated/output'; +import { + type TypeCreatePlanSchema, + createPlanSchema, +} from '@/schemas/plan/create-plan.schema'; + +export const CreatePlanForm = () => { + const t = useTranslations('dashboard.plans.createForm'); + + const [isOpen, setIsOpen] = useState(false); + const { refetch } = useFindMySponsorshipPlansQuery(); + + const form = useForm({ + resolver: zodResolver(createPlanSchema), + defaultValues: { + title: '', + description: '', + price: 0, + }, + }); + + const [create, { loading: isLoadingCreate }] = useCreateSponsorshipPlanMutation({ + onCompleted() { + setIsOpen(false); + form.reset(); + void refetch(); + toast.success(t('successMessage')); + }, + onError() { + toast.error(t('errorMessage')); + }, + }); + + const { isValid } = form.formState; + + function onSubmit(data: TypeCreatePlanSchema) { + void create({ variables: { data } }); + } + + return ( + + + + + + + + + {t('heading')} + + + +
+ + ( + + + {t('titleLabel')} + + + + + + + + {t('titleDescription')} + + + )} + /> + + ( + + + {t('descriptionLabel')} + + + +