Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions frontend/app/[locale]/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function ResumePage() {
<div className={styles.container}>
<header className={styles.header}>
<h1>Хохлов Дмитрий</h1>
<p>Frontend Developer (React)</p>
<p>FullStack Developer (TypeScript, Rust)</p>
<button onClick={() => setShowContact(!showContact)} className={styles.contactButton}>
{showContact ? 'Скрыть контакты' : 'Показать контакты'}
</button>
Expand All @@ -30,7 +30,7 @@ export default function ResumePage() {
<section>
<h2>Обо мне</h2>
<p>
Я опытный frontend разработчик с более чем четырехлетним опытом в создании
Я опытный FullStack разработчик с более чем четырехлетним опытом в создании
эффективных веб-приложений. Мои навыки охватывают весь цикл разработки, от концепции и дизайна до реализации и поддержки.
</p>
<p>
Expand Down
84 changes: 61 additions & 23 deletions frontend/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
/* eslint-disable @next/next/no-page-custom-font */
import { ThemeProvider } from "@/components/theme-provider";
import StoreProvider from "@/store/StoreProvider";
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar7';
import { Metadata } from "next";
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, unstable_setRequestLocale } from 'next-intl/server';
import Header from '../_components/Header/Header';
import Sidebar from '../_components/Sidebar/Sidebar';
import '../_styles/globals.css';
import { GoogleAnalytics } from '@next/third-parties/google'
import { getMessages, getTranslations, unstable_setRequestLocale } from 'next-intl/server';
import Script from "next/script.js";
import { GoogleAnalytics } from '@next/third-parties/google';
import { ThemeProvider } from "@/components/theme-provider";
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar7';
import StoreProvider from "@/store/StoreProvider";
import { locales as appLocales } from '@/types/i18n';
import "@fontsource/material-symbols-outlined"
import Header from '../_components/Header/Header';
import NowPlaying from "../_components/NowPlaying/NowPlaying";
import AppSidebar from '../_components/Sidebar/Sidebar';
import '../_styles/globals.css';
import "@fontsource/material-symbols-outlined";

type Props = {
children: React.ReactNode;
params: Promise<{ locale: string }>;
params: { locale: string };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'Metadata' });

return {
title: {
default: t('defaultTitle'),
template: `%s | ${t('template')}`,
},
description: t('defaultDescription'),
keywords: ['Дмитрий Хохлов', 'Dmitry Khokhlov', 'FullStack Developer', 'React', 'Next.js', 'TypeScript', 'Портфолио', 'Rust'],
authors: [{ name: 'Dmitry Khokhlov', url: 'https://diametrfq.ru' }],
creator: 'Dmitry Khokhlov',
};
}

export function generateStaticParams() {
return appLocales.map((locale) => ({locale}));
}

export default async function LocaleLayout({ children, params }: Props) {
// КЛЮЧЕВОЕ ИЗМЕНЕНИЕ: НЕ деструктурируем в сигнатуре.
const { locale } = await params;

unstable_setRequestLocale(locale);
Expand All @@ -35,31 +53,29 @@ export default async function LocaleLayout({ children, params }: Props) {
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap"
/>

<Script
id="yandex-metrika"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
m[i].l=1*new Date();
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}\n
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})\n
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");\n

ym(102356747, "init", {\n
clickmap:true,\n
trackLinks:true,\n
accurateTrackBounce:true,\n
webvisor:true\n
});\n
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}\\n
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})\\n
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");\\n
ym(102356747, "init", {\\n
clickmap:true,\\n
trackLinks:true,\\n
accurateTrackBounce:true,\\n
webvisor:true\\n
});\\n
`
}}
/>
</head>
<GoogleAnalytics gaId="G-7VQWEH45FM"/>
<body>
<StoreProvider> {/* Redux нужен для других вещей, например, счетчика */}
<StoreProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
Expand All @@ -68,7 +84,7 @@ export default async function LocaleLayout({ children, params }: Props) {
>
<NextIntlClientProvider locale={locale} messages={messages}>
<SidebarProvider>
<Sidebar/>
<AppSidebar />
<SidebarInset>
<div>
<Header />
Expand All @@ -79,6 +95,28 @@ export default async function LocaleLayout({ children, params }: Props) {
</NextIntlClientProvider>
</ThemeProvider>
</StoreProvider>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "Person",
"name": "Dmitry Khokhlov",
"url": "https://diametrfq.ru",
"image": "https://diametrfq.ru/my-photo.jpg",
"sameAs": [
"https://github.com/DiametrFQ",
"https://www.linkedin.com/in/diametrfq",
"https://t.me/diametrfq"
],
"jobTitle": "FullStack Developer",
"worksFor": {
"@type": "Organization",
"name": "Cyberia"
}
})
}}
/>
<NowPlaying />
</body>
</html>
Expand Down
60 changes: 28 additions & 32 deletions frontend/app/_components/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar, // 1. ИМПОРТИРУЕМ ХУК useSidebar
useSidebar,
} from '@/components/ui/sidebar7';
import { DropdownMenu, DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu';
import { useLocale, useTranslations } from 'next-intl';
Expand All @@ -29,17 +29,15 @@ const items = [

const onlyEng = items.filter((item) => item.title !== 'telegram').map((item) => item.title);

// ↓↓↓ ВОТ ГЛАВНОЕ ИСПРАВЛЕНИЕ ↓↓↓
// Убираем все пропсы из сигнатуры. Компонент ничего не принимает.
export default function AppSidebar() {
const pathname = usePathname();
const locale = useLocale();
const t = useTranslations('SidebarNavigation');

// 2. ПОЛУЧАЕМ КОНТЕКСТ САЙДБАРА
const { isMobile, setOpenMobile } = useSidebar();

// 3. СОЗДАЕМ ОБРАБОТЧИК КЛИКА
const handleLinkClick = () => {
// Закрываем сайдбар только если мы на мобильном устройстве
if (isMobile) {
setOpenMobile(false);
}
Expand All @@ -58,33 +56,31 @@ export default function AppSidebar() {
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => (
(locale === 'en' && onlyEng.includes(item.title)) ||
(locale === 'ru')
) && (
<SidebarMenuItem key={item.title} className="stroke-pink-700">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton asChild>
<Link
href={`/${locale}${item.url}`}
className={
pathname === `/${locale}${item.url}` ||
(item.url === '/' && pathname === `/${locale}`)
? 'bg-accent text-accent-foreground'
: ''
}
// 4. ПРИМЕНЯЕМ ОБРАБОТЧИК КО ВСЕМ ССЫЛКАМ
onClick={handleLinkClick}
>
<span className="material-symbols-outlined">
{item.iconName}
</span>
<span>{t(`${item.title}.title`)}</span>
</Link>
</SidebarMenuButton>
</DropdownMenuTrigger>
</DropdownMenu>
</SidebarMenuItem>
((locale === 'en' && onlyEng.includes(item.title)) || locale === 'ru') && (
<SidebarMenuItem key={item.title} className="stroke-pink-700">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton asChild>
<Link
href={`/${locale}${item.url}`}
className={
pathname === `/${locale}${item.url}` ||
(item.url === '/' && pathname === `/${locale}`)
? 'bg-accent text-accent-foreground'
: ''
}
onClick={handleLinkClick}
>
<span className="material-symbols-outlined">
{item.iconName}
</span>
<span>{t(`${item.title}.title`)}</span>
</Link>
</SidebarMenuButton>
</DropdownMenuTrigger>
</DropdownMenu>
</SidebarMenuItem>
)
))}
</SidebarMenu>
</SidebarGroupContent>
Expand Down
9 changes: 5 additions & 4 deletions frontend/app/i18n.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
import {locales as appLocales, type locale as tyeLocale} from '@/types/i18n';
import {locales as appLocales, type locale as LocaleType} from '@/types/i18n';

export default getRequestConfig(async ({locale}) => {
if (!appLocales.includes(locale as tyeLocale)) notFound();
if (!appLocales.includes(locale as LocaleType)) notFound();

return {
locale,
messages: (await import(`../locales/${locale}.json`)).default
locale,
messages: (await import(`../locales/${locale}.json`)).default,
timeZone: 'Europe/Moscow'
};
});
44 changes: 17 additions & 27 deletions frontend/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
export default async function sitemap() {
return [
{
url: 'https://diametrfq/about',
lastModified: new Date()
},
{
url: 'https://diametrfq/contact',
lastModified: new Date()
},
{
url: 'https://diametrfq/dashboard',
lastModified: new Date()
},
{
url: 'https://diametrfq/portfolio',
lastModified: new Date()
},
{
url: 'https://diametrfq/telegram',
lastModified: new Date()
},
{
url: 'https://diametrfq/cats',
lastModified: new Date()
},
]
import { MetadataRoute } from 'next';
import { locales } from '@/types/i18n';

// Обязательно укажите здесь ваш настоящий домен!
const BASE_URL = 'https://diametrfq.ru';

export default function sitemap(): MetadataRoute.Sitemap {
const routes = ['/', '/about', '/contact', '/portfolio', '/telegram'];

const sitemapEntries = routes.flatMap((route) =>
locales.map((locale) => ({
url: `${BASE_URL}/${locale}${route === '/' ? '' : route}`,
lastModified: new Date(),
}))
);

return sitemapEntries;
}
41 changes: 41 additions & 0 deletions frontend/components/providers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client';

import { NextIntlClientProvider, AbstractIntlMessages } from 'next-intl';
import { ThemeProvider } from "@/components/theme-provider";
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar7';
import StoreProvider from "@/store/StoreProvider";
import Header from '@/app/_components/Header/Header';
import AppSidebar from '@/app/_components/Sidebar/Sidebar'; // Используем новое имя
import NowPlaying from '@/app/_components/NowPlaying/NowPlaying';

type Props = {
children: React.ReactNode;
locale: string;
messages: AbstractIntlMessages;
};

export default function Providers({ children, locale, messages }: Props) {
return (
<StoreProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<NextIntlClientProvider locale={locale} messages={messages}>
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<div>
<Header />
<main className="p-5">{children}</main>
</div>
</SidebarInset>
</SidebarProvider>
</NextIntlClientProvider>
</ThemeProvider>
<NowPlaying />
</StoreProvider>
);
}
5 changes: 5 additions & 0 deletions frontend/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@
"steam": "Steam",
"discord": "Discord"
}
},
"Metadata": {
"defaultTitle": "Dmitry Khokhlov — Fullstack Developer",
"template": "Dmitry Khokhlov",
"defaultDescription": "Portfolio of a fullstack developer Dmitry Khokhlov. Specializing in React, Next.js, and TypeScript. Open to job opportunities."
}
}
5 changes: 5 additions & 0 deletions frontend/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@
"steam": "Steam",
"discord": "Discord"
}
},
"Metadata": {
"defaultTitle": "Дмитрий Хохлов — Fullstack Разработчик",
"template": "Дмитрий Хохлов",
"defaultDescription": "Портфолио fullstack-разработчика. Специализируюсь на React, Next.js и TypeScript. Открыт к предложениям о работе."
}
}
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
Expand Down
5 changes: 5 additions & 0 deletions frontend/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
User-agent: *
Disallow: /_next/
Disallow: /api/

Sitemap: https://diametrfq.ru/sitemap.xml