From 68bbef35ee368265500f3680d42cad6e5b034b7d Mon Sep 17 00:00:00 2001 From: "Andrey K. Choi" Date: Sun, 9 Nov 2025 22:15:48 +0900 Subject: [PATCH 1/2] localization && navbar fix --- docker-compose.yml | 8 +- frontend/linktree-frontend/.dockerignore | 2 - frontend/linktree-frontend/Dockerfile | 29 +- .../(protected)/dashboard/DashboardClient.tsx | 208 ++++++------ .../src/app/(protected)/dashboard/page.tsx | 21 +- .../src/app/auth/login/page.tsx | 30 +- .../src/app/auth/register/page.tsx | 42 +-- .../src/app/components/CustomizationPanel.tsx | 80 ++--- .../src/app/components/LanguageSelector.tsx | 55 ++-- .../src/app/components/LayoutWrapper.tsx | 171 +++++----- .../src/app/components/LayoutWrapper.tsx.bak | 302 ++++++++++++++++++ .../src/app/components/Navbar.tsx | 28 ++ .../src/app/components/Providers.tsx | 2 + .../src/app/components/ThemeToggle.css | 123 +++++++ .../src/app/components/ThemeToggle.tsx | 54 ++-- .../src/app/components/footer.tsx | 12 +- .../linktree-frontend/src/app/locales/en.json | 154 ++++++++- .../linktree-frontend/src/app/locales/ja.json | 88 ++++- .../linktree-frontend/src/app/locales/ko.json | 90 +++++- .../linktree-frontend/src/app/locales/ru.json | 156 ++++++++- .../linktree-frontend/src/app/locales/zh.json | 88 ++++- frontend/linktree-frontend/src/app/page.tsx | 169 +--------- 22 files changed, 1386 insertions(+), 526 deletions(-) create mode 100644 frontend/linktree-frontend/src/app/components/LayoutWrapper.tsx.bak create mode 100644 frontend/linktree-frontend/src/app/components/Navbar.tsx create mode 100644 frontend/linktree-frontend/src/app/components/ThemeToggle.css diff --git a/docker-compose.yml b/docker-compose.yml index e40416a..6589fec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: - media_volume:/app/storage - static_volume:/app/staticfiles env_file: - - .env + - .env.local ports: - "8000:8000" depends_on: @@ -22,7 +22,7 @@ services: volumes: - postgres_data:/var/lib/postgresql/data/ env_file: - - .env + - .env.local environment: - POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256 restart: unless-stopped @@ -34,9 +34,9 @@ services: ports: - "3000:3000" environment: - - NEXT_PUBLIC_API_URL=https://links.shareon.kr + - NEXT_PUBLIC_API_URL=http://localhost:8000 env_file: - - .env + - .env.local restart: unless-stopped depends_on: - web diff --git a/frontend/linktree-frontend/.dockerignore b/frontend/linktree-frontend/.dockerignore index 1ff2662..5034fb3 100644 --- a/frontend/linktree-frontend/.dockerignore +++ b/frontend/linktree-frontend/.dockerignore @@ -3,7 +3,6 @@ node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* -package-lock.json yarn.lock # Production build @@ -81,7 +80,6 @@ __tests__/ # TypeScript *.tsbuildinfo -tsconfig.json # Storybook .storybook/ diff --git a/frontend/linktree-frontend/Dockerfile b/frontend/linktree-frontend/Dockerfile index 24b1300..6b60296 100644 --- a/frontend/linktree-frontend/Dockerfile +++ b/frontend/linktree-frontend/Dockerfile @@ -1,12 +1,24 @@ -FROM node:20-alpine +# Этап 1: Установка зависимостей +FROM node:20-alpine as deps WORKDIR /app # Копирование package.json и package-lock.json COPY package*.json ./ -# Установка зависимостей -RUN npm install +# Установка зависимостей с очисткой кеша +RUN npm ci --omit=dev && npm cache clean --force + +# Этап 2: Сборка приложения +FROM node:20-alpine as builder + +WORKDIR /app + +# Копирование package.json и package-lock.json +COPY package*.json ./ + +# Установка всех зависимостей (включая dev) +RUN npm ci # Копирование исходного кода COPY . . @@ -14,6 +26,17 @@ COPY . . # Сборка приложения RUN npm run build +# Этап 3: Финальный образ +FROM node:20-alpine as runner + +WORKDIR /app + +# Копирование зависимостей продакшена +COPY --from=deps /app/node_modules ./node_modules +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/package*.json ./ + EXPOSE 3000 CMD ["npm", "start"] \ No newline at end of file diff --git a/frontend/linktree-frontend/src/app/(protected)/dashboard/DashboardClient.tsx b/frontend/linktree-frontend/src/app/(protected)/dashboard/DashboardClient.tsx index 996b1ab..c19b4c8 100644 --- a/frontend/linktree-frontend/src/app/(protected)/dashboard/DashboardClient.tsx +++ b/frontend/linktree-frontend/src/app/(protected)/dashboard/DashboardClient.tsx @@ -6,6 +6,8 @@ import Link from 'next/link' import Image from 'next/image' import { ProfileCard } from '../../components/ProfileCard' import { CustomizationPanel } from '../../components/CustomizationPanel' +import { Navbar } from '../../components/Navbar' +import { useLocale } from '../../contexts/LocaleContext' interface UserProfile { id: number @@ -72,6 +74,7 @@ interface DesignSettings { } export default function DashboardClient() { + const { t } = useLocale() const router = useRouter() const [user, setUser] = useState(null) const [groups, setGroups] = useState([]) @@ -174,7 +177,7 @@ export default function DashboardClient() { try { await navigator.clipboard.writeText(shareUrl) - alert('Ссылка скопирована в буфер обмена!') + alert(t('common.success') + ': ' + t('dashboard.shareUrl.copied')) } catch (err) { // Fallback для старых браузеров const textArea = document.createElement('textarea') @@ -183,7 +186,7 @@ export default function DashboardClient() { textArea.select() document.execCommand('copy') document.body.removeChild(textArea) - alert('Ссылка скопирована в буфер обмена!') + alert(t('common.success') + ': ' + t('dashboard.shareUrl.copied')) } } @@ -221,9 +224,9 @@ export default function DashboardClient() { fetch('/api/customization/settings/', { headers: { Authorization: `Bearer ${token}` } }), ]) .then(async ([uRes, gRes, lRes, dRes]) => { - if (!uRes.ok) throw new Error('Не удалось получить профиль') - if (!gRes.ok) throw new Error('Не удалось загрузить группы') - if (!lRes.ok) throw new Error('Не удалось загрузить ссылки') + if (!uRes.ok) throw new Error(t('common.error') + ': ' + t('auth.networkError')) + if (!gRes.ok) throw new Error(t('common.error') + ': ' + t('dashboard.groups')) + if (!lRes.ok) throw new Error(t('common.error') + ': ' + t('dashboard.links')) const userData = await uRes.json() const groupsData = await gRes.json() @@ -318,7 +321,7 @@ export default function DashboardClient() { await reloadData() } async function handleDeleteGroup(grp: Group) { - if (!confirm(`Удалить группу "${grp.name}"?`)) return + if (!confirm(t('common.confirm') + ` ${t('group.delete')} "${grp.name}"?`)) return const token = localStorage.getItem('token')! await fetch(`${API}/api/groups/${grp.id}/`, { method: 'DELETE', @@ -372,7 +375,7 @@ export default function DashboardClient() { await reloadData() } async function handleDeleteLink(link: LinkItem) { - if (!confirm(`Удалить ссылку "${link.title}"?`)) return + if (!confirm(t('common.confirm') + ` ${t('link.delete')} "${link.title}"?`)) return const token = localStorage.getItem('token')! await fetch(`${API}/api/links/${link.id}/`, { method: 'DELETE', @@ -419,11 +422,11 @@ export default function DashboardClient() { setShowProfileModal(false) } else { const error = await res.json() - alert('Ошибка: ' + JSON.stringify(error)) + alert(t('dashboard.error') + JSON.stringify(error)) } } - if (loading) return
Загрузка...
+ if (loading) return
{t('common.loading')}
if (error) return
{error}
// Функция расчета оптимального размера изображения для группы @@ -475,9 +478,9 @@ export default function DashboardClient() { const renderListLayout = () => (
-
Группы ссылок
+
{t('dashboard.groups')}
@@ -528,9 +531,9 @@ export default function DashboardClient() { const renderGridLayout = () => (
-
Группы ссылок
+
{t('dashboard.groups')}
@@ -599,7 +602,7 @@ export default function DashboardClient() { const renderCompactLayout = () => (
-
Группы ссылок
+
{t('dashboard.groups')}
@@ -647,9 +650,9 @@ export default function DashboardClient() { const renderCardsLayout = () => (
-
Группы ссылок
+
{t('dashboard.linkGroups')}
@@ -673,7 +676,7 @@ export default function DashboardClient() {
{group.name}
- {group.links.length} ссылок + {t('dashboard.linksCount', { count: group.links.length })}
@@ -738,7 +741,7 @@ export default function DashboardClient() {
-
Группы
+
{t('dashboard.groups')}
@@ -830,9 +833,9 @@ export default function DashboardClient() { const renderMasonryLayout = () => (
-
Группы ссылок
+
{t('dashboard.groups')}
@@ -904,9 +907,9 @@ export default function DashboardClient() { const renderTimelineLayout = () => (
-
Группы ссылок
+
{t('dashboard.groups')}
@@ -929,7 +932,7 @@ export default function DashboardClient() {
{group.name}
- {group.links.length} ссылок + {t('dashboard.linksCount', { count: group.links.length })}
@@ -976,9 +979,9 @@ export default function DashboardClient() { const renderMagazineLayout = () => (
-
Группы ссылок
+
{t('dashboard.groups')}
@@ -1006,7 +1009,7 @@ export default function DashboardClient() { {group.name}

- {group.links.length} ссылок в этой группе + {t('dashboard.linksInGroup', { count: group.links.length })}

{group.links.slice(0, 3).map(link => ( @@ -1017,7 +1020,7 @@ export default function DashboardClient() {
))} {group.links.length > 3 && ( - и еще {group.links.length - 3}... + {t('dashboard.andMore', { count: group.links.length - 3 })} )}
@@ -1087,6 +1090,8 @@ export default function DashboardClient() { style={containerStyle} suppressHydrationWarning={true} > + + {user && (
-

Ваши ссылки

+

{t('dashboard.title')}

- Panel state: {showCustomizationPanel ? 'Open' : 'Closed'} + Panel state: {showCustomizationPanel ? t('dashboard.panelOpen') : t('dashboard.panelClosed')}
@@ -1141,18 +1146,18 @@ export default function DashboardClient() {
-
{groupModalMode === 'add' ? 'Добавить группу' : 'Редактировать группу'}
+
{groupModalMode === 'add' ? t('group.create') : t('group.edit')}
- +
- +