From ed59327cb792fb58560996221558b4584a51ab41 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Thu, 14 Aug 2025 05:45:17 +0300 Subject: [PATCH] [frontend]: add chat settings page --- .../src/app/(site)/dashboard/chat/page.tsx | 20 +++ .../chat/settings/ChangeChatSettings.tsx | 132 ++++++++++++++++++ frontend/src/graphql/generated/output.ts | 85 ++++++++++- .../mutations/chat/ChangeChatSettings.graphql | 5 + .../mutations/chat/SendChatMessage.graphql | 5 + .../graphql/queries/user/FindProfile.graphql | 3 + .../chat/change-chat.settings.schema.ts | 11 ++ .../src/schemas/chat/send-message.schema.ts | 7 + 8 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/(site)/dashboard/chat/page.tsx create mode 100644 frontend/src/components/features/chat/settings/ChangeChatSettings.tsx create mode 100644 frontend/src/graphql/mutations/chat/ChangeChatSettings.graphql create mode 100644 frontend/src/graphql/mutations/chat/SendChatMessage.graphql create mode 100644 frontend/src/schemas/chat/change-chat.settings.schema.ts create mode 100644 frontend/src/schemas/chat/send-message.schema.ts diff --git a/frontend/src/app/(site)/dashboard/chat/page.tsx b/frontend/src/app/(site)/dashboard/chat/page.tsx new file mode 100644 index 0000000..857275c --- /dev/null +++ b/frontend/src/app/(site)/dashboard/chat/page.tsx @@ -0,0 +1,20 @@ +import { getTranslations } from 'next-intl/server'; + +import { ChangeChatSettings } from '@/components/features/chat/settings/ChangeChatSettings'; +import { NO_INDEX_PAGE } from '@/libs/constants/seo.constants'; + +import type { Metadata } from 'next'; + +export async function generateMetadata(): Promise { + const t = await getTranslations('dashboard.chat.header'); + + return { + title: t('heading'), + description: t('description'), + ...NO_INDEX_PAGE, + }; +} + +const ChatSettingsPage = () => ; + +export default ChatSettingsPage; diff --git a/frontend/src/components/features/chat/settings/ChangeChatSettings.tsx b/frontend/src/components/features/chat/settings/ChangeChatSettings.tsx new file mode 100644 index 0000000..6f824a0 --- /dev/null +++ b/frontend/src/components/features/chat/settings/ChangeChatSettings.tsx @@ -0,0 +1,132 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useTranslations } from 'next-intl'; +import { useForm } from 'react-hook-form'; +import { toast } from 'sonner'; + +import { Form, FormField } from '@/components/ui/common/Form'; +import { Heading } from '@/components/ui/elements/Heading'; +import { + ToggleCard, + ToggleCardSkeleton, +} from '@/components/ui/elements/ToggleCard'; +import { useChangeChatSettingsMutation } from '@/graphql/generated/output'; +import { useCurrent } from '@/hooks/useCurrent'; +import { changeChatSettingsSchema } from '@/schemas/chat/change-chat.settings.schema'; + +import type { TypeChangeChatSettingsSchema } from '@/schemas/chat/change-chat.settings.schema'; + +export const ChangeChatSettings = () => { + const t = useTranslations('dashboard.chat'); + + const { user, isLoadingProfile } = useCurrent(); + + const form = useForm({ + resolver: zodResolver(changeChatSettingsSchema), + values: { + isChatEnable: user?.stream.isChatEnable ?? false, + isChatFollowersOnly: user?.stream.isChatFollowersOnly ?? false, + isChatPremiumFollowersOnly: + user?.stream.isChatPremiumFollowersOnly ?? false, + }, + }); + + const [update, { loading: isLoadingUpdate }] = useChangeChatSettingsMutation({ + onCompleted(data) { + toast.success(t('successMessage')); + }, + onError() { + toast.error(t('errorMessage')); + }, + }); + + function onChange( + field: keyof TypeChangeChatSettingsSchema, + value: boolean, + ) { + form.setValue(field, value); + + update({ + variables: { + data: { ...form.getValues(), [field]: value }, + }, + }); + } + + return ( +
+ + +
+ {isLoadingProfile + ? Array.from({ length: 3 }).map((_, index) => ( + // eslint-disable-next-line react/no-array-index-key + + )) + : ( +
+ ( + { onChange('isChatEnable', value); }} + /> + )} + /> + + ( + { onChange('isChatFollowersOnly', value); }} + /> + )} + /> + + ( + { + onChange( + 'isChatPremiumFollowersOnly', + value, + ); + }} + /> + )} + /> + + )} +
+
+ ); +}; diff --git a/frontend/src/graphql/generated/output.ts b/frontend/src/graphql/generated/output.ts index 5ae52d8..9a318b2 100644 --- a/frontend/src/graphql/generated/output.ts +++ b/frontend/src/graphql/generated/output.ts @@ -643,6 +643,20 @@ export type VerifyAccountMutationVariables = Exact<{ export type VerifyAccountMutation = { __typename?: 'Mutation', verifyAccount: { __typename?: 'AuthModel', message?: string | null, user?: { __typename?: 'UserModel', isEmailVerified: boolean } | null } }; +export type ChangeChatSettingsMutationVariables = Exact<{ + data: ChangeChatSettingsInput; +}>; + + +export type ChangeChatSettingsMutation = { __typename?: 'Mutation', changeChatSettings: { __typename?: 'StreamModel', id: string } }; + +export type SendChatMessageMutationVariables = Exact<{ + data: SendMessageInput; +}>; + + +export type SendChatMessageMutation = { __typename?: 'Mutation', sendChatMessage: { __typename?: 'ChatMessageModel', streamId: string } }; + export type CreateIngressMutationVariables = Exact<{ ingressType: Scalars['Float']['input']; }>; @@ -768,7 +782,7 @@ export type FindUnreadNotificationsCountQuery = { __typename?: 'Query', findUnre export type FindProfileQueryVariables = Exact<{ [key: string]: never; }>; -export type FindProfileQuery = { __typename?: 'Query', findProfile: { __typename?: 'UserModel', avatar?: string | null, bio?: string | null, createdAt: any, email: string, id: string, name: string, updatedAt: any, isEmailVerified: boolean, isTotpEnabled: boolean, isVerified: boolean, displayName: string, socialLink: Array<{ __typename?: 'SocialLinkModel', title: string, url: string, position: number }>, notificationSettings?: { __typename?: 'NotificationSettingsModel', siteNotifications: boolean, telegramNotifications: boolean } | null, stream: { __typename?: 'StreamModel', serverUrl?: string | null, key?: string | null } } }; +export type FindProfileQuery = { __typename?: 'Query', findProfile: { __typename?: 'UserModel', avatar?: string | null, bio?: string | null, createdAt: any, email: string, id: string, name: string, updatedAt: any, isEmailVerified: boolean, isTotpEnabled: boolean, isVerified: boolean, displayName: string, socialLink: Array<{ __typename?: 'SocialLinkModel', title: string, url: string, position: number }>, notificationSettings?: { __typename?: 'NotificationSettingsModel', siteNotifications: boolean, telegramNotifications: boolean } | null, stream: { __typename?: 'StreamModel', serverUrl?: string | null, key?: string | null, isChatEnable: boolean, isChatFollowersOnly: boolean, isChatPremiumFollowersOnly: boolean } } }; export type FindSessionsByUserQueryVariables = Exact<{ [key: string]: never; }>; @@ -1053,6 +1067,72 @@ export function useVerifyAccountMutation(baseOptions?: Apollo.MutationHookOption export type VerifyAccountMutationHookResult = ReturnType; export type VerifyAccountMutationResult = Apollo.MutationResult; export type VerifyAccountMutationOptions = Apollo.BaseMutationOptions; +export const ChangeChatSettingsDocument = gql` + mutation ChangeChatSettings($data: ChangeChatSettingsInput!) { + changeChatSettings(data: $data) { + id + } +} + `; +export type ChangeChatSettingsMutationFn = Apollo.MutationFunction; + +/** + * __useChangeChatSettingsMutation__ + * + * To run a mutation, you first call `useChangeChatSettingsMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useChangeChatSettingsMutation` 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 [changeChatSettingsMutation, { data, loading, error }] = useChangeChatSettingsMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useChangeChatSettingsMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(ChangeChatSettingsDocument, options); + } +export type ChangeChatSettingsMutationHookResult = ReturnType; +export type ChangeChatSettingsMutationResult = Apollo.MutationResult; +export type ChangeChatSettingsMutationOptions = Apollo.BaseMutationOptions; +export const SendChatMessageDocument = gql` + mutation SendChatMessage($data: SendMessageInput!) { + sendChatMessage(data: $data) { + streamId + } +} + `; +export type SendChatMessageMutationFn = Apollo.MutationFunction; + +/** + * __useSendChatMessageMutation__ + * + * To run a mutation, you first call `useSendChatMessageMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useSendChatMessageMutation` 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 [sendChatMessageMutation, { data, loading, error }] = useSendChatMessageMutation({ + * variables: { + * data: // value for 'data' + * }, + * }); + */ +export function useSendChatMessageMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(SendChatMessageDocument, options); + } +export type SendChatMessageMutationHookResult = ReturnType; +export type SendChatMessageMutationResult = Apollo.MutationResult; +export type SendChatMessageMutationOptions = Apollo.BaseMutationOptions; export const CreateIngressDocument = gql` mutation CreateIngress($ingressType: Float!) { createIngress(ingressType: $ingressType) @@ -1757,6 +1837,9 @@ export const FindProfileDocument = gql` stream { serverUrl key + isChatEnable + isChatFollowersOnly + isChatPremiumFollowersOnly } } } diff --git a/frontend/src/graphql/mutations/chat/ChangeChatSettings.graphql b/frontend/src/graphql/mutations/chat/ChangeChatSettings.graphql new file mode 100644 index 0000000..91eb37e --- /dev/null +++ b/frontend/src/graphql/mutations/chat/ChangeChatSettings.graphql @@ -0,0 +1,5 @@ +mutation ChangeChatSettings($data: ChangeChatSettingsInput!) { + changeChatSettings(data: $data) { + id + } +} diff --git a/frontend/src/graphql/mutations/chat/SendChatMessage.graphql b/frontend/src/graphql/mutations/chat/SendChatMessage.graphql new file mode 100644 index 0000000..eb21057 --- /dev/null +++ b/frontend/src/graphql/mutations/chat/SendChatMessage.graphql @@ -0,0 +1,5 @@ +mutation SendChatMessage($data: SendMessageInput!) { + sendChatMessage(data: $data) { + streamId + } +} diff --git a/frontend/src/graphql/queries/user/FindProfile.graphql b/frontend/src/graphql/queries/user/FindProfile.graphql index d41fe85..82ee588 100644 --- a/frontend/src/graphql/queries/user/FindProfile.graphql +++ b/frontend/src/graphql/queries/user/FindProfile.graphql @@ -23,6 +23,9 @@ query FindProfile { stream { serverUrl key + isChatEnable + isChatFollowersOnly + isChatPremiumFollowersOnly } } } diff --git a/frontend/src/schemas/chat/change-chat.settings.schema.ts b/frontend/src/schemas/chat/change-chat.settings.schema.ts new file mode 100644 index 0000000..3172673 --- /dev/null +++ b/frontend/src/schemas/chat/change-chat.settings.schema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const changeChatSettingsSchema = z.object({ + isChatEnable: z.boolean(), + isChatFollowersOnly: z.boolean(), + isChatPremiumFollowersOnly: z.boolean(), +}); + +export type TypeChangeChatSettingsSchema = z.infer< + typeof changeChatSettingsSchema +>; diff --git a/frontend/src/schemas/chat/send-message.schema.ts b/frontend/src/schemas/chat/send-message.schema.ts new file mode 100644 index 0000000..4d02a12 --- /dev/null +++ b/frontend/src/schemas/chat/send-message.schema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const sendMessageSchema = z.object({ + text: z.string().min(1), +}); + +export type TypeSendMessageSchema = z.infer;