diff --git a/frontend/package.json b/frontend/package.json
index daf0fbf..28cb974 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -3,8 +3,8 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "yarn run codegen && next dev --turbopack",
- "build": "yarn run codegen && next build",
+ "dev": "next dev --turbopack",
+ "build": "next build",
"start": "next start",
"lint": "next lint",
"codegen": "graphql-codegen --config graphql.config.ts --watch"
diff --git a/frontend/src/components/layout/sidebar/ChannelItem.tsx b/frontend/src/components/layout/sidebar/ChannelItem.tsx
new file mode 100644
index 0000000..f19412c
--- /dev/null
+++ b/frontend/src/components/layout/sidebar/ChannelItem.tsx
@@ -0,0 +1,78 @@
+'use client';
+
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+
+import { Button } from '@/components/ui/common/Button';
+import { Skeleton } from '@/components/ui/common/Skeleton';
+import { ChannelAvatar } from '@/components/ui/elements/ChannelAvatar';
+import { ChannelVerified } from '@/components/ui/elements/ChannelVerified';
+import { Hint } from '@/components/ui/elements/Hint';
+import { LiveBadge } from '@/components/ui/elements/LiveBadge';
+import { useSidebar } from '@/hooks/useSidebar';
+import { cn } from '@/utils/tw-merge';
+
+import type { FindRecommendedChannelsQuery } from '@/graphql/generated/output';
+
+type ChannelItemProps = {
+ channel: FindRecommendedChannelsQuery['findRecommendedChannels'][0];
+};
+
+export const ChannelItem = ({ channel }: ChannelItemProps) => {
+ const pathname = usePathname();
+ const { isCollapsed } = useSidebar();
+
+ const isActive = pathname === `/${channel.name}`;
+
+ return isCollapsed
+ ? (
+
+
+
+
+
+ )
+ : (
+
+ );
+};
+
+export const ChannelItemSkeleton = () => ;
diff --git a/frontend/src/components/layout/sidebar/RecommendedChannels.tsx b/frontend/src/components/layout/sidebar/RecommendedChannels.tsx
new file mode 100644
index 0000000..cd4fbf1
--- /dev/null
+++ b/frontend/src/components/layout/sidebar/RecommendedChannels.tsx
@@ -0,0 +1,39 @@
+'use client';
+
+import { useTranslations } from 'next-intl';
+
+import { Separator } from '@/components/ui/common/Separator';
+import { useFindRecommendedChannelsQuery } from '@/graphql/generated/output';
+import { useSidebar } from '@/hooks/useSidebar';
+
+import { ChannelItem, ChannelItemSkeleton } from './ChannelItem';
+
+export const RecommendedChannels = () => {
+ const t = useTranslations('layout.sidebar.recommended');
+
+ const { isCollapsed } = useSidebar();
+
+ const { data, loading: isLoadingRecommended } = useFindRecommendedChannelsQuery();
+ const channels = data?.findRecommendedChannels ?? [];
+
+ return (
+
+
+
+ {!isCollapsed && (
+
+ {t('heading')}
+
+ )}
+
+ {isLoadingRecommended
+ ? Array.from({ length: 7 }).map((_, index) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))
+ : channels.map((channel) => (
+
+ ))}
+
+ );
+};
diff --git a/frontend/src/components/layout/sidebar/UserNav.tsx b/frontend/src/components/layout/sidebar/UserNav.tsx
index 3e10e7d..130e6a2 100644
--- a/frontend/src/components/layout/sidebar/UserNav.tsx
+++ b/frontend/src/components/layout/sidebar/UserNav.tsx
@@ -1,6 +1,7 @@
import { Folder, Home, Radio } from 'lucide-react';
import { useTranslations } from 'next-intl';
+import { RecommendedChannels } from './RecommendedChannels';
import { SidebarItem } from './SidebarItem';
import type { Route } from './route.interface';
@@ -32,7 +33,7 @@ export const UserNav = () => {
))}
- {/* */}
+
);
};
diff --git a/frontend/src/components/ui/common/Skeleton.tsx b/frontend/src/components/ui/common/Skeleton.tsx
new file mode 100644
index 0000000..56cd665
--- /dev/null
+++ b/frontend/src/components/ui/common/Skeleton.tsx
@@ -0,0 +1,15 @@
+import { cn } from '@/utils/tw-merge';
+
+import type { HTMLAttributes } from 'react';
+
+const Skeleton = ({ className, ...props }: HTMLAttributes) => (
+
+);
+
+export { Skeleton };
diff --git a/frontend/src/components/ui/elements/ChannelVerified.tsx b/frontend/src/components/ui/elements/ChannelVerified.tsx
new file mode 100644
index 0000000..87516e5
--- /dev/null
+++ b/frontend/src/components/ui/elements/ChannelVerified.tsx
@@ -0,0 +1,34 @@
+import { type VariantProps, cva } from 'class-variance-authority';
+import { Check } from 'lucide-react';
+
+import { cn } from '@/utils/tw-merge';
+
+const channelVerifiedSizes = cva('', {
+ variants: {
+ size: {
+ sm: 'size-3',
+ default: 'size-4',
+ },
+ },
+ defaultVariants: {
+ size: 'default',
+ },
+});
+
+type ChannelVerifiedProps = VariantProps & {};
+
+export const ChannelVerified = ({ size }: ChannelVerifiedProps) => (
+
+
+
+);
diff --git a/frontend/src/components/ui/elements/LiveBadge.tsx b/frontend/src/components/ui/elements/LiveBadge.tsx
new file mode 100644
index 0000000..36b5c09
--- /dev/null
+++ b/frontend/src/components/ui/elements/LiveBadge.tsx
@@ -0,0 +1,13 @@
+'use client';
+
+import { useTranslations } from 'next-intl';
+
+export const LiveBadge = () => {
+ const t = useTranslations('components.liveBadge');
+
+ return (
+
+ {t('text')}
+
+ );
+};
diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts
deleted file mode 100644
index b50b401..0000000
--- a/frontend/src/global.d.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import type messages from '../public/languages/ru.json';
-
-const locales = ['ru', 'en'] as const;
-
-declare module 'next-intl' {
- type AppConfig = {
- Locale: (typeof locales)[number];
- Messages: typeof messages;
- };
-}
diff --git a/frontend/src/graphql/generated/output.ts b/frontend/src/graphql/generated/output.ts
index fa40bae..62d428d 100644
--- a/frontend/src/graphql/generated/output.ts
+++ b/frontend/src/graphql/generated/output.ts
@@ -636,6 +636,11 @@ export type VerifyAccountMutationVariables = Exact<{
export type VerifyAccountMutation = { __typename?: 'Mutation', verifyAccount: { __typename?: 'AuthModel', message?: string | null, user?: { __typename?: 'UserModel', isEmailVerified: boolean } | null } };
+export type FindRecommendedChannelsQueryVariables = Exact<{ [key: string]: never; }>;
+
+
+export type FindRecommendedChannelsQuery = { __typename?: 'Query', findRecommendedChannels: Array<{ __typename?: 'UserModel', id: string, name: string, avatar?: string | null, isVerified: boolean, stream: { __typename?: 'StreamModel', isLive: boolean } }> };
+
export type FindChannelByUsernameQueryVariables = Exact<{
name: Scalars['String']['input'];
}>;
@@ -890,6 +895,51 @@ export function useVerifyAccountMutation(baseOptions?: Apollo.MutationHookOption
export type VerifyAccountMutationHookResult = ReturnType;
export type VerifyAccountMutationResult = Apollo.MutationResult;
export type VerifyAccountMutationOptions = Apollo.BaseMutationOptions;
+export const FindRecommendedChannelsDocument = gql`
+ query FindRecommendedChannels {
+ findRecommendedChannels {
+ id
+ name
+ avatar
+ isVerified
+ stream {
+ isLive
+ }
+ }
+}
+ `;
+
+/**
+ * __useFindRecommendedChannelsQuery__
+ *
+ * To run a query within a React component, call `useFindRecommendedChannelsQuery` and pass it any options that fit your needs.
+ * When your component renders, `useFindRecommendedChannelsQuery` 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 } = useFindRecommendedChannelsQuery({
+ * variables: {
+ * },
+ * });
+ */
+export function useFindRecommendedChannelsQuery(baseOptions?: Apollo.QueryHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useQuery(FindRecommendedChannelsDocument, options);
+ }
+export function useFindRecommendedChannelsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) {
+ const options = {...defaultOptions, ...baseOptions}
+ return Apollo.useLazyQuery(FindRecommendedChannelsDocument, options);
+ }
+export function useFindRecommendedChannelsSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions) {
+ const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
+ return Apollo.useSuspenseQuery(FindRecommendedChannelsDocument, options);
+ }
+export type FindRecommendedChannelsQueryHookResult = ReturnType;
+export type FindRecommendedChannelsLazyQueryHookResult = ReturnType;
+export type FindRecommendedChannelsSuspenseQueryHookResult = ReturnType;
+export type FindRecommendedChannelsQueryResult = Apollo.QueryResult;
export const FindChannelByUsernameDocument = gql`
query FindChannelByUsername($name: String!) {
findChannelByUsername(name: $name) {
diff --git a/frontend/src/graphql/queries/channels/FindRecommendedChannels.graphql b/frontend/src/graphql/queries/channels/FindRecommendedChannels.graphql
new file mode 100644
index 0000000..0a607a5
--- /dev/null
+++ b/frontend/src/graphql/queries/channels/FindRecommendedChannels.graphql
@@ -0,0 +1,11 @@
+query FindRecommendedChannels {
+ findRecommendedChannels {
+ id
+ name
+ avatar
+ isVerified
+ stream {
+ isLive
+ }
+ }
+}
diff --git a/frontend/src/hooks/useCurrent.ts b/frontend/src/hooks/useCurrent.ts
index becf550..24d7868 100644
--- a/frontend/src/hooks/useCurrent.ts
+++ b/frontend/src/hooks/useCurrent.ts
@@ -19,12 +19,9 @@ export function useCurrent() {
useEffect(() => {
if (error) {
- console.log('error', error);
if (isAuthenticated) {
- console.log('CLEAR');
void clear();
}
- console.log('exit');
exit();
}
}, [isAuthenticated, exit, clear]);
diff --git a/frontend/src/libs/apollo-client.ts b/frontend/src/libs/apollo-client.ts
index 7c39c6f..119fd29 100644
--- a/frontend/src/libs/apollo-client.ts
+++ b/frontend/src/libs/apollo-client.ts
@@ -1,4 +1,5 @@
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
+
import { SERVER_URL } from './constants/url.constants';
const httpLink = createHttpLink({
diff --git a/frontend/src/libs/i18n/global.d.ts b/frontend/src/libs/i18n/global.d.ts
new file mode 100644
index 0000000..2351127
--- /dev/null
+++ b/frontend/src/libs/i18n/global.d.ts
@@ -0,0 +1,10 @@
+import type { Language } from './config';
+import type messages from '../../../public/languages/ru.json';
+
+export declare module 'next-intl' {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+ interface AppConfig {
+ Locale: Language;
+ Messages: typeof messages;
+ }
+}