[frontend]: add graphql and i18n

This commit is contained in:
Sergey Krylov 2025-07-30 06:11:27 +03:00
parent d579e44014
commit 9dce83aa83
19 changed files with 6515 additions and 6265 deletions

10
frontend/apollo.config.js Normal file
View File

@ -0,0 +1,10 @@
require('dotenv/config')
module.exports = {
service: {
endpoint: {
url: process.env.NEXT_PUBLIC_SERVER_URL,
skipSSLValidation: true
}
}
}

View File

@ -11,6 +11,9 @@ const compat = new FlatCompat({
const eslintConfig = [ const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"), ...compat.extends("next/core-web-vitals", "next/typescript"),
{
ignores: ['src/graphql/generated']
}
]; ];
export default eslintConfig; export default eslintConfig;

View File

@ -0,0 +1,19 @@
import { CodegenConfig } from '@graphql-codegen/cli';
import 'dotenv/config'
const config: CodegenConfig = {
schema: process.env.NEXT_PUBLIC_SERVER_URL,
documents: ['./src/graphql/**/*.graphql'],
generates: {
'./src/graphql/generated/output.ts': {
plugins: [
'typescript',
'typescript-operations',
'typescript-react-apollo'
]
}
},
ignoreNoDocuments: true
}
export default config

6
frontend/graphql.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module '*.graphql' {
import {DocumentNode} from 'graphql'
const schema: DocumentNode
export = schema
}

View File

@ -1,7 +1,10 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./src/libs/i18n/request.ts')
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* config options here */ reactStrictMode: true
}; };
export default nextConfig; export default withNextIntl(nextConfig);

File diff suppressed because it is too large Load Diff

View File

@ -3,25 +3,33 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "yarn run codegen && next dev --turbopack",
"build": "next build", "build": "yarn run codegen && next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"codegen": "graphql-codegen --config graphql.config.ts"
}, },
"dependencies": { "dependencies": {
"react": "19.1.0", "@apollo/client": "3.13.9",
"react-dom": "19.1.0", "@graphql-codegen/cli": "5.0.7",
"next": "15.4.4" "@graphql-codegen/typescript": "4.1.6",
"@graphql-codegen/typescript-operations": "4.6.1",
"@graphql-codegen/typescript-react-apollo": "4.3.3",
"graphql": "16.11.0",
"next": "15.4.5",
"next-intl": "^4.3.4",
"react": "19.1.1",
"react-dom": "19.1.1"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5", "@eslint/eslintrc": "3.3.1",
"@types/node": "^20", "@tailwindcss/postcss": "4.1.11",
"@types/react": "^19", "@types/node": "22.17.0",
"@types/react-dom": "^19", "@types/react": "19.1.9",
"@tailwindcss/postcss": "^4", "@types/react-dom": "19.1.7",
"tailwindcss": "^4", "eslint": "9.32.0",
"eslint": "^9",
"eslint-config-next": "15.4.4", "eslint-config-next": "15.4.4",
"@eslint/eslintrc": "^3" "tailwindcss": "4.1.11",
"typescript": "5.8.3"
} }
} }

View File

@ -0,0 +1,5 @@
{
"home": {
"title": "Home page"
}
}

View File

@ -0,0 +1,5 @@
{
"home": {
"title": "Домашняя страница"
}
}

View File

@ -1,4 +1,7 @@
import ApolloClientProvider from '@/providers/ApolloClientProvider';
import type { Metadata } from "next"; import type { Metadata } from "next";
import { NextIntlClientProvider } from 'next-intl';
import { getLocale, getMessages } from 'next-intl/server';
import { Geist, Geist_Mono } from "next/font/google"; import { Geist, Geist_Mono } from "next/font/google";
import "../styles/globals.css"; import "../styles/globals.css";
@ -17,17 +20,24 @@ export const metadata: Metadata = {
description: "Generated by create next app", description: "Generated by create next app",
}; };
export default function RootLayout({ export default async function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
const locale = await getLocale()
const messages = await getMessages()
return ( return (
<html lang="en"> <html lang={locale}>
<body <body
className={`${geistSans.variable} ${geistMono.variable} antialiased`} className={`${geistSans.variable} ${geistMono.variable} antialiased`}
> >
<ApolloClientProvider>
<NextIntlClientProvider messages={messages}>
{children} {children}
</NextIntlClientProvider>
</ApolloClientProvider>
</body> </body>
</html> </html>
); );

View File

@ -1,103 +1,13 @@
import Image from "next/image"; 'use client'
import { useTranslations } from 'next-intl';
export default function Home() { export default function Home() {
return ( const t = useTranslations('home')
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row"> return (
<a <div>
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto" {t('title')}
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div> </div>
); );
} }

View File

@ -0,0 +1,646 @@
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T | null;
export type InputMaybe<T> = Maybe<T>;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
const defaultOptions = {} as const;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string; }
String: { input: string; output: string; }
Boolean: { input: boolean; output: boolean; }
Int: { input: number; output: number; }
Float: { input: number; output: number; }
DateTime: { input: any; output: any; }
Upload: { input: any; output: any; }
};
export type AuthModel = {
__typename?: 'AuthModel';
message?: Maybe<Scalars['String']['output']>;
user?: Maybe<UserModel>;
};
export type CategoryModel = {
__typename?: 'CategoryModel';
createdAt: Scalars['DateTime']['output'];
description?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
slug: Scalars['String']['output'];
streams: Array<StreamModel>;
thumbnailUrl: Scalars['String']['output'];
title: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
};
export type ChangeChatSettingsInput = {
isChatEnable: Scalars['Boolean']['input'];
isChatFollowersOnly: Scalars['Boolean']['input'];
isChatPremiumFollowersOnly: Scalars['Boolean']['input'];
};
export type ChangeEmailInput = {
email: Scalars['String']['input'];
};
export type ChangeNotificationSettingsInput = {
siteNotifications: Scalars['Boolean']['input'];
telegramNotifications: Scalars['Boolean']['input'];
};
export type ChangeNotificationsSettingsResponse = {
__typename?: 'ChangeNotificationsSettingsResponse';
notificationSettings: NotificationSettingsModel;
telegramAuthToken?: Maybe<Scalars['String']['output']>;
};
export type ChangePasswordInput = {
newPassword: Scalars['String']['input'];
oldPassword: Scalars['String']['input'];
};
export type ChangeProfileInfoInput = {
bio: Scalars['String']['input'];
displayName: Scalars['String']['input'];
name: Scalars['String']['input'];
};
export type ChangeStreamInfoInput = {
categoryId: Scalars['String']['input'];
title: Scalars['String']['input'];
};
export type ChatMessageModel = {
__typename?: 'ChatMessageModel';
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
streamId: Scalars['ID']['output'];
text: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
userId: Scalars['ID']['output'];
};
export type CreatePlanInput = {
description?: InputMaybe<Scalars['String']['input']>;
price: Scalars['Float']['input'];
title: Scalars['String']['input'];
};
export type CreateUserInput = {
email: Scalars['String']['input'];
name: Scalars['String']['input'];
password: Scalars['String']['input'];
};
export type DeactivateAccountInput = {
email: Scalars['String']['input'];
password: Scalars['String']['input'];
pin?: InputMaybe<Scalars['String']['input']>;
};
export type DeviceModel = {
__typename?: 'DeviceModel';
browser: Scalars['String']['output'];
os: Scalars['String']['output'];
type: Scalars['String']['output'];
};
export type EnableTotpInput = {
pin: Scalars['String']['input'];
secret: Scalars['String']['input'];
};
export type FilterInput = {
searchTerm?: InputMaybe<Scalars['String']['input']>;
skip?: InputMaybe<Scalars['Float']['input']>;
take?: InputMaybe<Scalars['Float']['input']>;
};
export type FollowModel = {
__typename?: 'FollowModel';
createdAt: Scalars['DateTime']['output'];
follower: UserModel;
followerId: Scalars['ID']['output'];
following: UserModel;
followingId: Scalars['ID']['output'];
id: Scalars['ID']['output'];
updatedAt: Scalars['DateTime']['output'];
};
export type GenerateStreamTokenInput = {
channelId: Scalars['String']['input'];
userId: Scalars['String']['input'];
};
export type GenerateTokenModel = {
__typename?: 'GenerateTokenModel';
token: Scalars['String']['output'];
};
export type LocationModel = {
__typename?: 'LocationModel';
city: Scalars['String']['output'];
country: Scalars['String']['output'];
latitude: Scalars['Float']['output'];
longitude: Scalars['Float']['output'];
};
export type LoginInput = {
login: Scalars['String']['input'];
password: Scalars['String']['input'];
pin?: InputMaybe<Scalars['String']['input']>;
};
export type MakePaymentModel = {
__typename?: 'MakePaymentModel';
url: Scalars['String']['output'];
};
export type Mutation = {
__typename?: 'Mutation';
changeChatSettings: StreamModel;
changeEmail: UserModel;
changeNotificationSettigs: ChangeNotificationsSettingsResponse;
changePassword: UserModel;
changeProfileAvatar: Scalars['Boolean']['output'];
changeProfileInfo: UserModel;
changeStreamInfo: StreamModel;
changeStreamThumbnail: Scalars['Boolean']['output'];
clearSessionCookie: Scalars['Boolean']['output'];
createIngress: Scalars['Boolean']['output'];
createSocialLink: SocialLinkModel;
createSponsorshipPlan: PlanModel;
createUser: UserModel;
deactivateAccount: AuthModel;
disableTotp: Scalars['Boolean']['output'];
enableTotp: Scalars['Boolean']['output'];
followChannel: FollowModel;
generateStreamToken: GenerateTokenModel;
loginUser: AuthModel;
logoutUser: Scalars['Boolean']['output'];
makePayment: MakePaymentModel;
removeProfileAvatar: Scalars['Boolean']['output'];
removeSession: Scalars['Boolean']['output'];
removeSocialLink: Scalars['Boolean']['output'];
removeSponsorshipPlan: PlanModel;
removeStreamThumbnail: Scalars['Boolean']['output'];
reorderSocialLink: Scalars['Boolean']['output'];
resetPassword: Scalars['Boolean']['output'];
sendChatMessage: ChatMessageModel;
setNewPassword: Scalars['Boolean']['output'];
unfollowChannel: FollowModel;
updateSocialLink: SocialLinkModel;
verifyAccount: AuthModel;
};
export type MutationChangeChatSettingsArgs = {
data: ChangeChatSettingsInput;
};
export type MutationChangeEmailArgs = {
data: ChangeEmailInput;
};
export type MutationChangeNotificationSettigsArgs = {
data: ChangeNotificationSettingsInput;
};
export type MutationChangePasswordArgs = {
data: ChangePasswordInput;
};
export type MutationChangeProfileAvatarArgs = {
avatar: Scalars['Upload']['input'];
};
export type MutationChangeProfileInfoArgs = {
data: ChangeProfileInfoInput;
};
export type MutationChangeStreamInfoArgs = {
data: ChangeStreamInfoInput;
};
export type MutationChangeStreamThumbnailArgs = {
thumbnail: Scalars['Upload']['input'];
};
export type MutationCreateIngressArgs = {
ingressType: Scalars['Float']['input'];
};
export type MutationCreateSocialLinkArgs = {
data: SocialLinkInput;
};
export type MutationCreateSponsorshipPlanArgs = {
data: CreatePlanInput;
};
export type MutationCreateUserArgs = {
data: CreateUserInput;
};
export type MutationDeactivateAccountArgs = {
data: DeactivateAccountInput;
};
export type MutationEnableTotpArgs = {
data: EnableTotpInput;
};
export type MutationFollowChannelArgs = {
channelId: Scalars['String']['input'];
};
export type MutationGenerateStreamTokenArgs = {
data: GenerateStreamTokenInput;
};
export type MutationLoginUserArgs = {
data: LoginInput;
};
export type MutationMakePaymentArgs = {
planId: Scalars['String']['input'];
};
export type MutationRemoveSessionArgs = {
id: Scalars['String']['input'];
};
export type MutationRemoveSocialLinkArgs = {
id: Scalars['String']['input'];
};
export type MutationRemoveSponsorshipPlanArgs = {
planId: Scalars['String']['input'];
};
export type MutationReorderSocialLinkArgs = {
list: Array<SocialLinkOrderInput>;
};
export type MutationResetPasswordArgs = {
data: ResetPasswordInput;
};
export type MutationSendChatMessageArgs = {
data: SendMessageInput;
};
export type MutationSetNewPasswordArgs = {
data: NewPasswordInput;
};
export type MutationUnfollowChannelArgs = {
channelId: Scalars['String']['input'];
};
export type MutationUpdateSocialLinkArgs = {
data: SocialLinkInput;
id: Scalars['String']['input'];
};
export type MutationVerifyAccountArgs = {
data: VerificationInput;
};
export type NewPasswordInput = {
password: Scalars['String']['input'];
passwordRepeat: Scalars['String']['input'];
token: Scalars['String']['input'];
};
export type NotificationModel = {
__typename?: 'NotificationModel';
createdAt: Scalars['DateTime']['output'];
id: Scalars['String']['output'];
isRead: Scalars['Boolean']['output'];
text: Scalars['String']['output'];
type: NotificationType;
updatedAt: Scalars['DateTime']['output'];
user: UserModel;
userId: Scalars['String']['output'];
};
export type NotificationSettingsModel = {
__typename?: 'NotificationSettingsModel';
createdAt: Scalars['DateTime']['output'];
id: Scalars['String']['output'];
siteNotifications: Scalars['Boolean']['output'];
telegramNotifications: Scalars['Boolean']['output'];
updatedAt: Scalars['DateTime']['output'];
user: UserModel;
userId: Scalars['String']['output'];
};
export enum NotificationType {
EnableTwoFactor = 'ENABLE_TWO_FACTOR',
NewFollower = 'NEW_FOLLOWER',
NewSponsorship = 'NEW_SPONSORSHIP',
StreamStart = 'STREAM_START',
VerifiedChannel = 'VERIFIED_CHANNEL'
}
export type PlanModel = {
__typename?: 'PlanModel';
channel: UserModel;
channelId: Scalars['ID']['output'];
createdAt: Scalars['DateTime']['output'];
description?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
price: Scalars['Float']['output'];
stripePlanId: Scalars['ID']['output'];
stripeProductId: Scalars['ID']['output'];
title: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
};
export type Query = {
__typename?: 'Query';
findAllCategories: Array<CategoryModel>;
findAllStreams: Array<StreamModel>;
findCategoryBySlug: CategoryModel;
findChannelByUsername: UserModel;
findChannelFollowersCount: Scalars['Float']['output'];
findCurrentSession: SessionModel;
findMessagesByStream: Array<ChatMessageModel>;
findMyFollowers: Array<FollowModel>;
findMyFollowings: Array<FollowModel>;
findMySponsors: Array<SubscriptionModel>;
findMySponsorshipPlans: Array<PlanModel>;
findMyTransactions: Array<TransactionModel>;
findNotificationByUser: Array<NotificationModel>;
findProfile: UserModel;
findRandomCategories: Array<CategoryModel>;
findRandomStreams: Array<StreamModel>;
findRecommendedChannels: Array<UserModel>;
findSessionsByUser: Array<SessionModel>;
findSocialLinks: Array<SocialLinkModel>;
findSponsorsByChannel: Array<SubscriptionModel>;
findUnreadNotificationsCount: Scalars['Float']['output'];
generateTotpSecret: TotpModel;
};
export type QueryFindAllStreamsArgs = {
filters: FilterInput;
};
export type QueryFindCategoryBySlugArgs = {
slug: Scalars['String']['input'];
};
export type QueryFindChannelByUsernameArgs = {
name: Scalars['String']['input'];
};
export type QueryFindChannelFollowersCountArgs = {
channelId: Scalars['String']['input'];
};
export type QueryFindMessagesByStreamArgs = {
streamId: Scalars['String']['input'];
};
export type QueryFindSponsorsByChannelArgs = {
channelId: Scalars['String']['input'];
};
export type ResetPasswordInput = {
email: Scalars['String']['input'];
};
export type SendMessageInput = {
streamId: Scalars['String']['input'];
text: Scalars['String']['input'];
};
export type SessionMetadataModel = {
__typename?: 'SessionMetadataModel';
device: DeviceModel;
ip: Scalars['String']['output'];
location: LocationModel;
};
export type SessionModel = {
__typename?: 'SessionModel';
createdAt: Scalars['String']['output'];
id: Scalars['ID']['output'];
metadata: SessionMetadataModel;
userId: Scalars['String']['output'];
};
export type SocialLinkInput = {
title: Scalars['String']['input'];
url: Scalars['String']['input'];
};
export type SocialLinkModel = {
__typename?: 'SocialLinkModel';
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
position: Scalars['Float']['output'];
title: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
url: Scalars['String']['output'];
userId: Scalars['ID']['output'];
};
export type SocialLinkOrderInput = {
id: Scalars['String']['input'];
position: Scalars['Float']['input'];
};
export type StreamModel = {
__typename?: 'StreamModel';
category: CategoryModel;
categoryId: Scalars['ID']['output'];
chatMessages: Array<ChatMessageModel>;
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
ingressId?: Maybe<Scalars['String']['output']>;
isChatEnable: Scalars['Boolean']['output'];
isChatFollowersOnly: Scalars['Boolean']['output'];
isChatPremiumFollowersOnly: Scalars['Boolean']['output'];
isLive: Scalars['Boolean']['output'];
key?: Maybe<Scalars['String']['output']>;
serverUrl?: Maybe<Scalars['String']['output']>;
thumbnailUrl?: Maybe<Scalars['String']['output']>;
title: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
user: UserModel;
userId: Scalars['ID']['output'];
};
export type Subscription = {
__typename?: 'Subscription';
chatMessageAdded: ChatMessageModel;
};
export type SubscriptionChatMessageAddedArgs = {
streamId: Scalars['String']['input'];
};
export type SubscriptionModel = {
__typename?: 'SubscriptionModel';
channel: UserModel;
channelId: Scalars['String']['output'];
createdAt: Scalars['DateTime']['output'];
expiresAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
plan: PlanModel;
planId: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
user: UserModel;
userId: Scalars['String']['output'];
};
export type TotpModel = {
__typename?: 'TotpModel';
qrcodeUrl: Scalars['String']['output'];
secret: Scalars['String']['output'];
};
export type TransactionModel = {
__typename?: 'TransactionModel';
amount: Scalars['Float']['output'];
createdAt: Scalars['DateTime']['output'];
currency: Scalars['String']['output'];
id: Scalars['ID']['output'];
status: TransactionStatus;
stripeSubscriptionId: Scalars['ID']['output'];
updatedAt: Scalars['DateTime']['output'];
user: UserModel;
userId: Scalars['ID']['output'];
};
export enum TransactionStatus {
Expired = 'EXPIRED',
Failed = 'FAILED',
Pending = 'PENDING',
Success = 'SUCCESS'
}
export type UserModel = {
__typename?: 'UserModel';
avatar?: Maybe<Scalars['String']['output']>;
bio?: Maybe<Scalars['String']['output']>;
createdAt: Scalars['DateTime']['output'];
deactivatedAt?: Maybe<Scalars['DateTime']['output']>;
displayName: Scalars['String']['output'];
email: Scalars['String']['output'];
followers: Array<FollowModel>;
followings: Array<FollowModel>;
id: Scalars['ID']['output'];
isDeactivated: Scalars['Boolean']['output'];
isEmailVerified: Scalars['Boolean']['output'];
isTotpEnabled: Scalars['Boolean']['output'];
isVerified: Scalars['Boolean']['output'];
name: Scalars['String']['output'];
notification: Array<NotificationModel>;
notificationSettings: NotificationSettingsModel;
password: Scalars['String']['output'];
socialLink: Array<SocialLinkModel>;
stream: StreamModel;
telegramId?: Maybe<Scalars['String']['output']>;
totpSecret?: Maybe<Scalars['String']['output']>;
updatedAt: Scalars['DateTime']['output'];
};
export type VerificationInput = {
token: Scalars['String']['input'];
};
export type FindChannelByUsernameQueryVariables = Exact<{
name: Scalars['String']['input'];
}>;
export type FindChannelByUsernameQuery = { __typename?: 'Query', findChannelByUsername: { __typename?: 'UserModel', name: string, avatar?: string | null, displayName: string, stream: { __typename?: 'StreamModel', title: string } } };
export const FindChannelByUsernameDocument = gql`
query FindChannelByUsername($name: String!) {
findChannelByUsername(name: $name) {
name
avatar
displayName
stream {
title
}
}
}
`;
/**
* __useFindChannelByUsernameQuery__
*
* To run a query within a React component, call `useFindChannelByUsernameQuery` and pass it any options that fit your needs.
* When your component renders, `useFindChannelByUsernameQuery` 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 } = useFindChannelByUsernameQuery({
* variables: {
* name: // value for 'name'
* },
* });
*/
export function useFindChannelByUsernameQuery(baseOptions: Apollo.QueryHookOptions<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables> & ({ variables: FindChannelByUsernameQueryVariables; skip?: boolean; } | { skip: boolean; }) ) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables>(FindChannelByUsernameDocument, options);
}
export function useFindChannelByUsernameLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables>(FindChannelByUsernameDocument, options);
}
export function useFindChannelByUsernameSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables>) {
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables>(FindChannelByUsernameDocument, options);
}
export type FindChannelByUsernameQueryHookResult = ReturnType<typeof useFindChannelByUsernameQuery>;
export type FindChannelByUsernameLazyQueryHookResult = ReturnType<typeof useFindChannelByUsernameLazyQuery>;
export type FindChannelByUsernameSuspenseQueryHookResult = ReturnType<typeof useFindChannelByUsernameSuspenseQuery>;
export type FindChannelByUsernameQueryResult = Apollo.QueryResult<FindChannelByUsernameQuery, FindChannelByUsernameQueryVariables>;

View File

@ -0,0 +1,10 @@
query FindChannelByUsername($name: String!) {
findChannelByUsername(name: $name) {
name
avatar
displayName
stream {
title
}
}
}

View File

@ -0,0 +1,11 @@
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
const httpLink = createHttpLink({
uri: process.env.NEXT_PUBLIC_SERVER_URL,
credentials: 'include'
})
export const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
})

View File

@ -0,0 +1,5 @@
export const COOKIE_NAME = 'language'
export const languages = ['ru', 'en'] as const
export const defaultLanguages: Language = 'ru';
export type Language = (typeof languages)[number]

View File

@ -0,0 +1,14 @@
'use server'
import { cookies } from "next/headers";
import { COOKIE_NAME, defaultLanguages, Language } from "./config";
export async function getCurrentLanguage() {
const cookieStore = await cookies();
return <Language>cookieStore.get(COOKIE_NAME)?.value ?? defaultLanguages;
}
export async function setCurrentLanguage(lang: Language) {
const cookieStore = await cookies();
return cookieStore.set(COOKIE_NAME, lang);
}

View File

@ -0,0 +1,12 @@
import { getRequestConfig } from 'next-intl/server';
import { getCurrentLanguage } from "./language"
export default getRequestConfig(async () => {
const locale = await getCurrentLanguage();
const messages = (await import(`../../../public/languages/${locale}.json`)).default;
return {
locale,
messages,
}
})

View File

@ -0,0 +1,17 @@
'use client'
import { client } from '@/libs/apollo-client';
import { ApolloProvider } from '@apollo/client';
import React, { PropsWithChildren } from 'react';
const ApolloClientProvider = (props: PropsWithChildren) => {
const {children} = props;
return (
<ApolloProvider client={client}>
{children}
</ApolloProvider>
);
};
export default ApolloClientProvider;

5705
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff