[frontend]: add sponsorship pages

This commit is contained in:
Sergey Krylov 2025-08-14 06:26:34 +03:00
parent 0f16f9d209
commit 37c6b318a6
23 changed files with 948 additions and 9 deletions

View File

@ -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<Metadata> {
const t = await getTranslations('dashboard.plans.header');
return {
title: t('heading'),
description: t('description'),
...NO_INDEX_PAGE,
};
}
const PlansPage = () => <PlansTable />;
export default PlansPage;

View File

@ -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<Metadata> {
const t = await getTranslations('dashboard.sponsors.header');
return {
title: t('heading'),
description: t('description'),
...NO_INDEX_PAGE,
};
}
const SponsorsPage = () => <SponsorsTable />;
export default SponsorsPage;

View File

@ -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<Metadata> {
const t = await getTranslations('dashboard.transactions.header');
return {
title: t('heading'),
description: t('description'),
...NO_INDEX_PAGE,
};
}
const TransactionsPage = () => <TransactionsTable />;
export default TransactionsPage;

View File

@ -14,6 +14,5 @@ export async function generateMetadata(): Promise<Metadata> {
};
}
export default function DeactivatePage() {
return <DeactivateForm />;
}
const DeactivatePage = () => <DeactivateForm />;
export default DeactivatePage;

View File

@ -63,7 +63,7 @@ export const DeactivateForm = () => {
const { isValid } = form.formState;
function onSubmit(data: TypeDeactivateSchema) {
deactivate({ variables: { data } });
void deactivate({ variables: { data } });
}
return (

View File

@ -47,7 +47,7 @@ export const ChangeChatSettings = () => {
) {
form.setValue(field, value);
update({
void update({
variables: {
data: { ...form.getValues(), [field]: value },
},

View File

@ -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<TypeCreatePlanSchema>({
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 (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button>
{t('trigger')}
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
{t('heading')}
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
className="space-y-6"
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>
{t('titleLabel')}
</FormLabel>
<FormControl>
<Input
disabled={isLoadingCreate}
placeholder={t('titlePlaceholder')}
{...field}
/>
</FormControl>
<FormDescription>
{t('titleDescription')}
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>
{t('descriptionLabel')}
</FormLabel>
<FormControl>
<Textarea
disabled={isLoadingCreate}
placeholder={t(
'descriptionPlaceholder',
)}
{...field}
/>
</FormControl>
<FormDescription>
{t('descriptionDescription')}
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="price"
render={({ field }) => (
<FormItem>
<FormLabel>
{t('priceLabel')}
</FormLabel>
<FormControl>
<Input
disabled={isLoadingCreate}
placeholder={t('priceLabel')}
type="number"
{...field}
/>
</FormControl>
<FormDescription>
{t('priceDescription')}
</FormDescription>
</FormItem>
)}
/>
<div className="flex justify-end">
<Button disabled={!isValid || isLoadingCreate}>
{t('submitButton')}
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@ -0,0 +1,135 @@
'use client';
import { MoreHorizontal, Trash } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { toast } from 'sonner';
import { Button } from '@/components/ui/common/Button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/common/DropdownMenu';
import {
DataTable,
DataTableSkeleton,
} from '@/components/ui/elements/DataTable';
import { Heading } from '@/components/ui/elements/Heading';
import {
type FindMySponsorshipPlansQuery,
useFindMySponsorshipPlansQuery,
useRemoveSponsorshipPlanMutation,
} from '@/graphql/generated/output';
import { useCurrent } from '@/hooks/useCurrent';
import { convertPrice } from '@/utils/convert-price';
import { formatDate } from '@/utils/format-date';
import { CreatePlanForm } from '../forms/CreatePlanForm';
import { VerifiedChannelAlert } from './VerifiedChannelAlert';
import type { ColumnDef } from '@tanstack/react-table';
export const PlansTable = () => {
const t = useTranslations('dashboard.plans');
const { user } = useCurrent();
const {
data,
loading: isLoadingPlans,
refetch,
} = useFindMySponsorshipPlansQuery();
const plans = data?.findMySponsorshipPlans ?? [];
const plansColumns: ColumnDef<
FindMySponsorshipPlansQuery['findMySponsorshipPlans'][0]
>[] = [
{
accessorKey: 'createdAt',
header: t('columns.date'),
cell: ({ row }) => formatDate(row.original.createdAt),
},
{
accessorKey: 'title',
header: t('columns.title'),
cell: ({ row }) => row.original.title,
},
{
accessorKey: 'price',
header: t('columns.price'),
cell: ({ row }) => convertPrice(row.original.price),
},
{
accessorKey: 'actions',
header: t('columns.actions'),
// eslint-disable-next-line react/no-unstable-nested-components
cell: ({ row }) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [remove, { loading: isLoadingRemove }] = useRemoveSponsorshipPlanMutation({
onCompleted() {
void refetch();
toast.success(t('columns.successMessage'));
},
onError() {
// @ts-ignore
toast.error(t('columns.errorMessage'));
},
});
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="size-8 p-0" variant="ghost">
<MoreHorizontal className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="right">
<DropdownMenuItem
className="text-red-500 focus:text-red-500"
disabled={isLoadingRemove}
onClick={async () => remove({
variables: { planId: row.original.id },
})}
>
<Trash className="mr-2 size-4" />
{t('columns.remove')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
},
},
];
return user?.isVerified
? (
<div className="lg:px-10">
<div className="block items-center justify-between space-y-3 lg:flex lg:space-y-0">
<Heading
description={t('header.description')}
size="lg"
title={t('header.heading')}
/>
<CreatePlanForm />
</div>
<div className="mt-5">
{isLoadingPlans
? (
<DataTableSkeleton />
)
: (
<DataTable columns={plansColumns} data={plans} />
)}
</div>
</div>
)
: (
<VerifiedChannelAlert />
);
};

View File

@ -0,0 +1,20 @@
import { ShieldAlert } from 'lucide-react';
import { useTranslations } from 'next-intl';
export const VerifiedChannelAlert = () => {
const t = useTranslations('dashboard.plans.alert');
return (
<div className="flex h-[75vh] w-full flex-col items-center justify-center">
<ShieldAlert className="size-20 text-muted-foreground" />
<h1 className="mt-6 text-2xl font-semibold">
{t('heading')}
</h1>
<p className="mt-3 w-full items-center text-center text-muted-foreground lg:w-[60%]">
{t('description')}
</p>
</div>
);
};

View File

@ -0,0 +1,112 @@
'use client';
import { MoreHorizontal, User } from 'lucide-react';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { Button } from '@/components/ui/common/Button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/common/DropdownMenu';
import { ChannelAvatar } from '@/components/ui/elements/ChannelAvatar';
import { ChannelVerified } from '@/components/ui/elements/ChannelVerified';
import {
DataTable,
DataTableSkeleton,
} from '@/components/ui/elements/DataTable';
import { Heading } from '@/components/ui/elements/Heading';
import {
type FindMySponsorsQuery,
useFindMySponsorsQuery,
} from '@/graphql/generated/output';
import { formatDate } from '@/utils/format-date';
import type { ColumnDef } from '@tanstack/react-table';
export const SponsorsTable = () => {
const t = useTranslations('dashboard.sponsors');
const { data, loading: isLoadingSponsors } = useFindMySponsorsQuery();
const sponsors = data?.findMySponsors ?? [];
const sponsorsColumns: ColumnDef<
FindMySponsorsQuery['findMySponsors'][0]
>[] = [
{
accessorKey: 'expiresAt',
header: t('columns.date'),
cell: ({ row }) => formatDate(row.original.expiresAt),
},
{
accessorKey: 'user',
header: t('columns.user'),
// eslint-disable-next-line react/no-unstable-nested-components
cell: ({ row }) => (
<div className="flex items-center gap-x-2">
<ChannelAvatar channel={row.original.user} size="sm" />
<h2>
{row.original.user.name}
</h2>
{row.original.user.isVerified ? <ChannelVerified size="sm" /> : null}
</div>
),
},
{
accessorKey: 'plan',
header: t('columns.plan'),
cell: ({ row }) => row.original.plan.title,
},
{
accessorKey: 'actions',
header: t('columns.actions'),
// eslint-disable-next-line react/no-unstable-nested-components
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="size-8 p-0" variant="ghost">
<MoreHorizontal className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="right">
<Link
href={`/${row.original.user.name}`}
target="_blank"
>
<DropdownMenuItem>
<User className="mr-2 size-4" />
{t('columns.viewChannel')}
</DropdownMenuItem>
</Link>
</DropdownMenuContent>
</DropdownMenu>
),
},
];
return (
<div className="lg:px-10">
<Heading
description={t('header.description')}
size="lg"
title={t('header.heading')}
/>
<div className="mt-5">
{isLoadingSponsors
? (
<DataTableSkeleton />
)
: (
<DataTable columns={sponsorsColumns} data={sponsors} />
)}
</div>
</div>
);
};

View File

@ -0,0 +1,115 @@
'use client';
import { useTranslations } from 'next-intl';
import {
DataTable,
DataTableSkeleton,
} from '@/components/ui/elements/DataTable';
import { Heading } from '@/components/ui/elements/Heading';
import {
type FindMyTransactionsQuery,
TransactionStatus,
useFindMyTransactionsQuery,
} from '@/graphql/generated/output';
import { convertPrice } from '@/utils/convert-price';
import { formatDate } from '@/utils/format-date';
import type { ColumnDef } from '@tanstack/react-table';
export const TransactionsTable = () => {
const t = useTranslations('dashboard.transactions');
const { data, loading: isLoadingTransactions } = useFindMyTransactionsQuery();
const transactions = data?.findMyTransactions ?? [];
const transactionsColumns: ColumnDef<
FindMyTransactionsQuery['findMyTransactions'][0]
>[] = [
{
accessorKey: 'createdAt',
header: t('columns.date'),
cell: ({ row }) => formatDate(row.original.createdAt),
},
{
accessorKey: 'status',
header: t('columns.status'),
// eslint-disable-next-line react/no-unstable-nested-components
cell: ({ row }) => {
const { status } = row.original;
// eslint-disable-next-line no-useless-assignment
let statusColor = '';
switch (status) {
case TransactionStatus.Success:
statusColor = 'text-green-500';
return (
<div className={`py-1.5 ${statusColor}`}>
{t('columns.success')}
</div>
);
case TransactionStatus.Pending:
statusColor = 'text-yellow-500';
return (
<div className={`py-1.5 ${statusColor}`}>
{t('columns.pending')}
</div>
);
case TransactionStatus.Failed:
statusColor = 'text-red-600';
return (
<div className={`py-1.5 ${statusColor}`}>
{t('columns.failed')}
</div>
);
case TransactionStatus.Expired:
statusColor = 'text-purple-500';
return (
<div className={`py-1.5 ${statusColor}`}>
{t('columns.expired')}
</div>
);
default:
statusColor = 'text-foreground';
return (
<div className={`py-1.5 ${statusColor}`}>
{status}
</div>
);
}
},
},
{
accessorKey: 'amount',
header: t('columns.amount'),
cell: ({ row }) => convertPrice(row.original.amount),
},
];
return (
<div className="lg:px-10">
<Heading
description={t('header.description')}
title={t('header.heading')}
/>
<div className="mt-5">
{isLoadingTransactions
? (
<DataTableSkeleton />
)
: (
<DataTable
columns={transactionsColumns}
data={transactions}
/>
)}
</div>
</div>
);
};

View File

@ -72,7 +72,6 @@ export const ChangeAvatarForm = () => {
return null;
}
return isLoadingProfile
? (
<ChangeAvatarFormSkeleton />

View File

@ -657,6 +657,27 @@ export type SendChatMessageMutationVariables = Exact<{
export type SendChatMessageMutation = { __typename?: 'Mutation', sendChatMessage: { __typename?: 'ChatMessageModel', streamId: string } };
export type CreateSponsorshipPlanMutationVariables = Exact<{
data: CreatePlanInput;
}>;
export type CreateSponsorshipPlanMutation = { __typename?: 'Mutation', createSponsorshipPlan: { __typename?: 'PlanModel', id: string } };
export type RemoveSponsorshipPlanMutationVariables = Exact<{
planId: Scalars['String']['input'];
}>;
export type RemoveSponsorshipPlanMutation = { __typename?: 'Mutation', removeSponsorshipPlan: { __typename?: 'PlanModel', id: string } };
export type MakePaymentMutationVariables = Exact<{
planId: Scalars['String']['input'];
}>;
export type MakePaymentMutation = { __typename?: 'Mutation', makePayment: { __typename?: 'MakePaymentModel', url: string } };
export type CreateIngressMutationVariables = Exact<{
ingressType: Scalars['Float']['input'];
}>;
@ -774,6 +795,21 @@ export type FindMyFollowingsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindMyFollowingsQuery = { __typename?: 'Query', findMyFollowings: Array<{ __typename?: 'FollowModel', createdAt: any, followingId: string }> };
export type FindMySponsorshipPlansQueryVariables = Exact<{ [key: string]: never; }>;
export type FindMySponsorshipPlansQuery = { __typename?: 'Query', findMySponsorshipPlans: Array<{ __typename?: 'PlanModel', id: string, createdAt: any, title: string, price: number }> };
export type FindMySponsorsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindMySponsorsQuery = { __typename?: 'Query', findMySponsors: Array<{ __typename?: 'SubscriptionModel', expiresAt: any, user: { __typename?: 'UserModel', name: string, avatar?: string | null, isVerified: boolean }, plan: { __typename?: 'PlanModel', title: string } }> };
export type FindMyTransactionsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindMyTransactionsQuery = { __typename?: 'Query', findMyTransactions: Array<{ __typename?: 'TransactionModel', createdAt: any, status: TransactionStatus, amount: number }> };
export type FindCurrentSessionQueryVariables = Exact<{ [key: string]: never; }>;
@ -1143,6 +1179,105 @@ export function useSendChatMessageMutation(baseOptions?: Apollo.MutationHookOpti
export type SendChatMessageMutationHookResult = ReturnType<typeof useSendChatMessageMutation>;
export type SendChatMessageMutationResult = Apollo.MutationResult<SendChatMessageMutation>;
export type SendChatMessageMutationOptions = Apollo.BaseMutationOptions<SendChatMessageMutation, SendChatMessageMutationVariables>;
export const CreateSponsorshipPlanDocument = gql`
mutation CreateSponsorshipPlan($data: CreatePlanInput!) {
createSponsorshipPlan(data: $data) {
id
}
}
`;
export type CreateSponsorshipPlanMutationFn = Apollo.MutationFunction<CreateSponsorshipPlanMutation, CreateSponsorshipPlanMutationVariables>;
/**
* __useCreateSponsorshipPlanMutation__
*
* To run a mutation, you first call `useCreateSponsorshipPlanMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateSponsorshipPlanMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createSponsorshipPlanMutation, { data, loading, error }] = useCreateSponsorshipPlanMutation({
* variables: {
* data: // value for 'data'
* },
* });
*/
export function useCreateSponsorshipPlanMutation(baseOptions?: Apollo.MutationHookOptions<CreateSponsorshipPlanMutation, CreateSponsorshipPlanMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateSponsorshipPlanMutation, CreateSponsorshipPlanMutationVariables>(CreateSponsorshipPlanDocument, options);
}
export type CreateSponsorshipPlanMutationHookResult = ReturnType<typeof useCreateSponsorshipPlanMutation>;
export type CreateSponsorshipPlanMutationResult = Apollo.MutationResult<CreateSponsorshipPlanMutation>;
export type CreateSponsorshipPlanMutationOptions = Apollo.BaseMutationOptions<CreateSponsorshipPlanMutation, CreateSponsorshipPlanMutationVariables>;
export const RemoveSponsorshipPlanDocument = gql`
mutation RemoveSponsorshipPlan($planId: String!) {
removeSponsorshipPlan(planId: $planId) {
id
}
}
`;
export type RemoveSponsorshipPlanMutationFn = Apollo.MutationFunction<RemoveSponsorshipPlanMutation, RemoveSponsorshipPlanMutationVariables>;
/**
* __useRemoveSponsorshipPlanMutation__
*
* To run a mutation, you first call `useRemoveSponsorshipPlanMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRemoveSponsorshipPlanMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [removeSponsorshipPlanMutation, { data, loading, error }] = useRemoveSponsorshipPlanMutation({
* variables: {
* planId: // value for 'planId'
* },
* });
*/
export function useRemoveSponsorshipPlanMutation(baseOptions?: Apollo.MutationHookOptions<RemoveSponsorshipPlanMutation, RemoveSponsorshipPlanMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<RemoveSponsorshipPlanMutation, RemoveSponsorshipPlanMutationVariables>(RemoveSponsorshipPlanDocument, options);
}
export type RemoveSponsorshipPlanMutationHookResult = ReturnType<typeof useRemoveSponsorshipPlanMutation>;
export type RemoveSponsorshipPlanMutationResult = Apollo.MutationResult<RemoveSponsorshipPlanMutation>;
export type RemoveSponsorshipPlanMutationOptions = Apollo.BaseMutationOptions<RemoveSponsorshipPlanMutation, RemoveSponsorshipPlanMutationVariables>;
export const MakePaymentDocument = gql`
mutation MakePayment($planId: String!) {
makePayment(planId: $planId) {
url
}
}
`;
export type MakePaymentMutationFn = Apollo.MutationFunction<MakePaymentMutation, MakePaymentMutationVariables>;
/**
* __useMakePaymentMutation__
*
* To run a mutation, you first call `useMakePaymentMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useMakePaymentMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [makePaymentMutation, { data, loading, error }] = useMakePaymentMutation({
* variables: {
* planId: // value for 'planId'
* },
* });
*/
export function useMakePaymentMutation(baseOptions?: Apollo.MutationHookOptions<MakePaymentMutation, MakePaymentMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<MakePaymentMutation, MakePaymentMutationVariables>(MakePaymentDocument, options);
}
export type MakePaymentMutationHookResult = ReturnType<typeof useMakePaymentMutation>;
export type MakePaymentMutationResult = Apollo.MutationResult<MakePaymentMutation>;
export type MakePaymentMutationOptions = Apollo.BaseMutationOptions<MakePaymentMutation, MakePaymentMutationVariables>;
export const CreateIngressDocument = gql`
mutation CreateIngress($ingressType: Float!) {
createIngress(ingressType: $ingressType)
@ -1770,6 +1905,136 @@ export type FindMyFollowingsQueryHookResult = ReturnType<typeof useFindMyFollowi
export type FindMyFollowingsLazyQueryHookResult = ReturnType<typeof useFindMyFollowingsLazyQuery>;
export type FindMyFollowingsSuspenseQueryHookResult = ReturnType<typeof useFindMyFollowingsSuspenseQuery>;
export type FindMyFollowingsQueryResult = Apollo.QueryResult<FindMyFollowingsQuery, FindMyFollowingsQueryVariables>;
export const FindMySponsorshipPlansDocument = gql`
query FindMySponsorshipPlans {
findMySponsorshipPlans {
id
createdAt
title
price
}
}
`;
/**
* __useFindMySponsorshipPlansQuery__
*
* To run a query within a React component, call `useFindMySponsorshipPlansQuery` and pass it any options that fit your needs.
* When your component renders, `useFindMySponsorshipPlansQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindMySponsorshipPlansQuery({
* variables: {
* },
* });
*/
export function useFindMySponsorshipPlansQuery(baseOptions?: Apollo.QueryHookOptions<FindMySponsorshipPlansQuery, FindMySponsorshipPlansQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindMySponsorshipPlansQuery, FindMySponsorshipPlansQueryVariables>(FindMySponsorshipPlansDocument, options);
}
export function useFindMySponsorshipPlansLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindMySponsorshipPlansQuery, FindMySponsorshipPlansQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindMySponsorshipPlansQuery, FindMySponsorshipPlansQueryVariables>(FindMySponsorshipPlansDocument, options);
}
export function useFindMySponsorshipPlansSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindMySponsorshipPlansQuery, FindMySponsorshipPlansQueryVariables>) {
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<FindMySponsorshipPlansQuery, FindMySponsorshipPlansQueryVariables>(FindMySponsorshipPlansDocument, options);
}
export type FindMySponsorshipPlansQueryHookResult = ReturnType<typeof useFindMySponsorshipPlansQuery>;
export type FindMySponsorshipPlansLazyQueryHookResult = ReturnType<typeof useFindMySponsorshipPlansLazyQuery>;
export type FindMySponsorshipPlansSuspenseQueryHookResult = ReturnType<typeof useFindMySponsorshipPlansSuspenseQuery>;
export type FindMySponsorshipPlansQueryResult = Apollo.QueryResult<FindMySponsorshipPlansQuery, FindMySponsorshipPlansQueryVariables>;
export const FindMySponsorsDocument = gql`
query FindMySponsors {
findMySponsors {
expiresAt
user {
name
avatar
isVerified
}
plan {
title
}
}
}
`;
/**
* __useFindMySponsorsQuery__
*
* To run a query within a React component, call `useFindMySponsorsQuery` and pass it any options that fit your needs.
* When your component renders, `useFindMySponsorsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindMySponsorsQuery({
* variables: {
* },
* });
*/
export function useFindMySponsorsQuery(baseOptions?: Apollo.QueryHookOptions<FindMySponsorsQuery, FindMySponsorsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindMySponsorsQuery, FindMySponsorsQueryVariables>(FindMySponsorsDocument, options);
}
export function useFindMySponsorsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindMySponsorsQuery, FindMySponsorsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindMySponsorsQuery, FindMySponsorsQueryVariables>(FindMySponsorsDocument, options);
}
export function useFindMySponsorsSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindMySponsorsQuery, FindMySponsorsQueryVariables>) {
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<FindMySponsorsQuery, FindMySponsorsQueryVariables>(FindMySponsorsDocument, options);
}
export type FindMySponsorsQueryHookResult = ReturnType<typeof useFindMySponsorsQuery>;
export type FindMySponsorsLazyQueryHookResult = ReturnType<typeof useFindMySponsorsLazyQuery>;
export type FindMySponsorsSuspenseQueryHookResult = ReturnType<typeof useFindMySponsorsSuspenseQuery>;
export type FindMySponsorsQueryResult = Apollo.QueryResult<FindMySponsorsQuery, FindMySponsorsQueryVariables>;
export const FindMyTransactionsDocument = gql`
query FindMyTransactions {
findMyTransactions {
createdAt
status
amount
}
}
`;
/**
* __useFindMyTransactionsQuery__
*
* To run a query within a React component, call `useFindMyTransactionsQuery` and pass it any options that fit your needs.
* When your component renders, `useFindMyTransactionsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindMyTransactionsQuery({
* variables: {
* },
* });
*/
export function useFindMyTransactionsQuery(baseOptions?: Apollo.QueryHookOptions<FindMyTransactionsQuery, FindMyTransactionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindMyTransactionsQuery, FindMyTransactionsQueryVariables>(FindMyTransactionsDocument, options);
}
export function useFindMyTransactionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindMyTransactionsQuery, FindMyTransactionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindMyTransactionsQuery, FindMyTransactionsQueryVariables>(FindMyTransactionsDocument, options);
}
export function useFindMyTransactionsSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindMyTransactionsQuery, FindMyTransactionsQueryVariables>) {
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<FindMyTransactionsQuery, FindMyTransactionsQueryVariables>(FindMyTransactionsDocument, options);
}
export type FindMyTransactionsQueryHookResult = ReturnType<typeof useFindMyTransactionsQuery>;
export type FindMyTransactionsLazyQueryHookResult = ReturnType<typeof useFindMyTransactionsLazyQuery>;
export type FindMyTransactionsSuspenseQueryHookResult = ReturnType<typeof useFindMyTransactionsSuspenseQuery>;
export type FindMyTransactionsQueryResult = Apollo.QueryResult<FindMyTransactionsQuery, FindMyTransactionsQueryVariables>;
export const FindCurrentSessionDocument = gql`
query FindCurrentSession {
findCurrentSession {

View File

@ -0,0 +1,5 @@
mutation CreateSponsorshipPlan($data: CreatePlanInput!) {
createSponsorshipPlan(data: $data) {
id
}
}

View File

@ -0,0 +1,5 @@
mutation RemoveSponsorshipPlan($planId: String!) {
removeSponsorshipPlan(planId: $planId) {
id
}
}

View File

@ -0,0 +1,5 @@
mutation MakePayment($planId: String!) {
makePayment(planId: $planId) {
url
}
}

View File

@ -0,0 +1,8 @@
query FindMySponsorshipPlans {
findMySponsorshipPlans {
id
createdAt
title
price
}
}

View File

@ -0,0 +1,13 @@
query FindMySponsors {
findMySponsors {
expiresAt
user {
name
avatar
isVerified
}
plan {
title
}
}
}

View File

@ -0,0 +1,7 @@
query FindMyTransactions {
findMyTransactions {
createdAt
status
amount
}
}

View File

@ -2,6 +2,8 @@ import { ApolloClient, InMemoryCache, split } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';
import createUploadLink from 'apollo-upload-client/createUploadLink.mjs';
import { OperationTypeNode } from 'graphql/language';
import { Kind } from 'graphql/language/kinds';
import { SERVER_URL, WEBSOCKET_URL } from './constants/url.constants';
@ -25,8 +27,8 @@ const splitLink = split(
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition'
&& definition.operation === 'subscription'
definition.kind === Kind.OPERATION_DEFINITION
&& definition.operation === OperationTypeNode.SUBSCRIPTION
);
},
wsLink,

View File

@ -1,3 +1,3 @@
export const SERVER_URL = process.env.NEXT_PUBLIC_SERVER_URL as string;
export const WEBSOCKET_URL = process.env.NEXT_PUBLIC_WEBSOCKET_URL as string
export const WEBSOCKET_URL = process.env.NEXT_PUBLIC_WEBSOCKET_URL as string;
export const MEDIA_URL = process.env.NEXT_PUBLIC_MEDIA_URL as string;

View File

@ -0,0 +1,9 @@
import { z } from 'zod';
export const createPlanSchema = z.object({
title: z.string().min(1),
description: z.string().optional(),
price: z.number().positive(),
});
export type TypeCreatePlanSchema = z.infer<typeof createPlanSchema>;

View File

@ -0,0 +1,6 @@
export function convertPrice(price: number) {
return price.toLocaleString('ru-RU', {
style: 'currency',
currency: 'RUB',
});
}