Skip to content

Commit 05080f5

Browse files
committed
feat: обновлен Dockerfile и добавлен компонент ExploreContent с поддержкой Suspense
1 parent 1f587c4 commit 05080f5

2 files changed

Lines changed: 58 additions & 84 deletions

File tree

frontend/Dockerfile

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ RUN npm install -g pnpm
99
# Копируем файлы зависимостей
1010
COPY package.json pnpm-lock.yaml ./
1111

12-
# Устанавливаем зависимости
12+
# Устанавливаем ВСЕ зависимости (нужны для билда)
1313
RUN pnpm install --frozen-lockfile
1414

1515
# Копируем исходный код
1616
COPY . .
1717

18-
# Аргумент для API URL (чтобы запечь в билд)
18+
# Аргумент для API URL
1919
ARG NEXT_PUBLIC_API_URL=http://localhost:8081/api/v1
2020
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
2121

@@ -30,17 +30,17 @@ WORKDIR /app
3030
# Устанавливаем pnpm
3131
RUN npm install -g pnpm
3232

33-
# Копируем конфиг и публичные файлы
34-
COPY --from=builder /app/next.config.ts ./
33+
# Копируем результаты сборки и необходимые файлы
34+
COPY --from=builder /app/.next ./.next
3535
COPY --from=builder /app/public ./public
3636
COPY --from=builder /app/package.json ./
3737
COPY --from=builder /app/pnpm-lock.yaml ./
38+
COPY --from=builder /app/next.config.ts ./
3839

39-
# Копируем собранное приложение
40-
COPY --from=builder /app/.next ./.next
41-
42-
# Устанавливаем только production зависимости
43-
RUN pnpm install --frozen-lockfile --prod
40+
# Устанавливаем зависимости.
41+
# ВАЖНО: Мы НЕ используем --prod здесь, так как для работы next.config.ts
42+
# в рантайме Next.js требует наличия typescript (который обычно в devDeps).
43+
RUN pnpm install --frozen-lockfile
4444

4545
# Expose порт
4646
EXPOSE 3000

frontend/src/app/explore/page.tsx

Lines changed: 49 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState, useEffect, useCallback } from "react";
3+
import { useState, useEffect, useCallback, Suspense } from "react";
44
import { useRouter, useSearchParams } from "next/navigation";
55
import { libraryApi, Library } from "@/lib/api";
66
import {
@@ -28,7 +28,7 @@ import {
2828
import LibraryCardSkeleton from "@/components/skeletons/LibraryCardSkeleton";
2929
import toast from "react-hot-toast";
3030

31-
export default function ExplorePage() {
31+
function ExploreContent() {
3232
const router = useRouter();
3333
const searchParams = useSearchParams();
3434
const [libraries, setLibraries] = useState<Library[]>([]);
@@ -75,7 +75,6 @@ export default function ExplorePage() {
7575
} else {
7676
setError("Не удалось загрузить данные");
7777
}
78-
// Toast показываем только если это не отмена запроса (если бы была)
7978
toast.error("Не удалось загрузить библиотеки");
8079
} finally {
8180
setLoading(false);
@@ -88,8 +87,6 @@ export default function ExplorePage() {
8887
const page = parseInt(searchParams.get("page") || "0");
8988
const searchQuery = searchParams.get("q") || "";
9089

91-
// Всегда синхронизируем input с URL (source of truth)
92-
// Это безопасно, так как эффект запускается только при изменении URL (searchParams)
9390
setQuery(searchQuery);
9491
setCurrentPage(page);
9592
fetchLibraries(page, searchQuery);
@@ -107,13 +104,10 @@ export default function ExplorePage() {
107104
_event: React.ChangeEvent<unknown>,
108105
page: number,
109106
) => {
110-
const newPage = page - 1; // Material-UI использует 1-based индексацию
111-
112-
// Обновляем URL
107+
const newPage = page - 1;
113108
const params = new URLSearchParams(searchParams.toString());
114109
params.set("page", newPage.toString());
115110
router.push(`/explore?${params.toString()}`);
116-
117111
window.scrollTo({ top: 0, behavior: "smooth" });
118112
};
119113

@@ -131,7 +125,6 @@ export default function ExplorePage() {
131125

132126
return (
133127
<Box sx={{ minHeight: "100vh", pb: 8 }}>
134-
{/* Hero Section */}
135128
<Box
136129
sx={{
137130
backgroundImage: `
@@ -179,15 +172,10 @@ export default function ExplorePage() {
179172
Находите и анализируйте Open Source библиотеки из различных
180173
экосистем
181174
</Typography>
182-
183-
{/* Search Box */}
184175
<Box
185176
component="form"
186177
onSubmit={handleSearch}
187-
sx={{
188-
maxWidth: 800,
189-
mx: "auto",
190-
}}
178+
sx={{ maxWidth: 800, mx: "auto" }}
191179
>
192180
<Box
193181
sx={{
@@ -211,7 +199,8 @@ export default function ExplorePage() {
211199
disableUnderline: true,
212200
startAdornment: (
213201
<InputAdornment position="start">
214-
<Search sx={{ color: "primary.main" }} />
202+
{" "}
203+
<Search sx={{ color: "primary.main" }} />{" "}
215204
</InputAdornment>
216205
),
217206
sx: { px: 2 },
@@ -221,17 +210,11 @@ export default function ExplorePage() {
221210
type="submit"
222211
variant="contained"
223212
size="large"
224-
sx={{
225-
px: 4,
226-
minWidth: 120,
227-
boxShadow: "none",
228-
}}
213+
sx={{ px: 4, minWidth: 120, boxShadow: "none" }}
229214
>
230215
Найти
231216
</Button>
232217
</Box>
233-
234-
{/* Quick Filters */}
235218
<Stack
236219
direction="row"
237220
spacing={1}
@@ -267,11 +250,9 @@ export default function ExplorePage() {
267250
</Container>
268251
</Box>
269252

270-
{/* Results Section */}
271253
<Container maxWidth="lg" sx={{ mt: 6 }}>
272254
{loading ? (
273255
<>
274-
{/* Results Header Skeleton */}
275256
<Box
276257
sx={{
277258
display: "flex",
@@ -281,11 +262,10 @@ export default function ExplorePage() {
281262
}}
282263
>
283264
<Typography variant="h6" fontWeight={600}>
284-
Загрузка...
265+
{" "}
266+
Загрузка...{" "}
285267
</Typography>
286268
</Box>
287-
288-
{/* Library Cards Skeleton */}
289269
<Box
290270
sx={{
291271
display: "grid",
@@ -305,18 +285,15 @@ export default function ExplorePage() {
305285
) : error ? (
306286
<Alert
307287
severity="error"
308-
sx={{
309-
borderRadius: 2,
310-
"& .MuiAlert-message": {
311-
width: "100%",
312-
},
313-
}}
288+
sx={{ borderRadius: 2, "& .MuiAlert-message": { width: "100%" } }}
314289
>
315290
<Typography variant="body1" fontWeight={600} gutterBottom>
316-
{error}
291+
{" "}
292+
{error}{" "}
317293
</Typography>
318294
<Typography variant="body2" sx={{ mt: 1 }}>
319-
Для запуска бэкенда выполните:
295+
{" "}
296+
Для запуска бэкенда выполните:{" "}
320297
</Typography>
321298
<Box
322299
component="code"
@@ -335,7 +312,6 @@ export default function ExplorePage() {
335312
</Alert>
336313
) : (
337314
<>
338-
{/* Results Header */}
339315
<Box
340316
sx={{
341317
display: "flex",
@@ -357,8 +333,6 @@ export default function ExplorePage() {
357333
Фильтры
358334
</Button>
359335
</Box>
360-
361-
{/* Library Cards */}
362336
<Box
363337
sx={{
364338
display: "grid",
@@ -392,7 +366,6 @@ export default function ExplorePage() {
392366
onClick={() => router.push(`/dashboard?libraryId=${lib.id}`)}
393367
>
394368
<CardContent sx={{ flexGrow: 1, p: 3 }}>
395-
{/* Header */}
396369
<Box
397370
sx={{
398371
display: "flex",
@@ -439,33 +412,27 @@ export default function ExplorePage() {
439412
fontWeight={600}
440413
sx={{ color: getHealthScoreColor(lib.healthScore) }}
441414
>
442-
{lib.healthScore}%
415+
{" "}
416+
{lib.healthScore}%{" "}
443417
</Typography>
444418
</Box>
445419
</Box>
446-
447-
{/* Title */}
448420
<Typography
449421
variant="h6"
450422
gutterBottom
451423
fontWeight={700}
452424
sx={{ mb: 1 }}
453425
>
454-
{lib.name}
426+
{" "}
427+
{lib.name}{" "}
455428
</Typography>
456-
457429
<Typography
458430
variant="caption"
459-
sx={{
460-
display: "block",
461-
color: "text.secondary",
462-
mb: 2,
463-
}}
431+
sx={{ display: "block", color: "text.secondary", mb: 2 }}
464432
>
465-
v{lib.version}
433+
{" "}
434+
v{lib.version}{" "}
466435
</Typography>
467-
468-
{/* Description */}
469436
<Typography
470437
variant="body2"
471438
color="text.secondary"
@@ -481,8 +448,6 @@ export default function ExplorePage() {
481448
>
482449
{lib.description || "Описание отсутствует"}
483450
</Typography>
484-
485-
{/* Metrics */}
486451
<Box sx={{ mb: 2 }}>
487452
<Stack direction="row" spacing={2} sx={{ mb: 1 }}>
488453
<Tooltip title="Рейтинг здоровья">
@@ -524,8 +489,6 @@ export default function ExplorePage() {
524489
)}
525490
</Stack>
526491
</Box>
527-
528-
{/* Footer */}
529492
<Box
530493
sx={{
531494
mt: "auto",
@@ -550,29 +513,23 @@ export default function ExplorePage() {
550513
</CardContent>
551514
</Card>
552515
))}
553-
554516
{libraries.length === 0 && (
555-
<Box
556-
sx={{
557-
gridColumn: "1 / -1",
558-
textAlign: "center",
559-
py: 10,
560-
}}
561-
>
517+
<Box sx={{ gridColumn: "1 / -1", textAlign: "center", py: 10 }}>
562518
<Search
563519
sx={{ fontSize: 64, color: "text.disabled", mb: 2 }}
564520
/>
565521
<Typography variant="h6" color="text.secondary" gutterBottom>
566-
Библиотеки не найдены
522+
{" "}
523+
Библиотеки не найдены{" "}
567524
</Typography>
568525
<Typography variant="body2" color="text.secondary">
569-
Попробуйте изменить запрос или выберите другую экосистему
526+
{" "}
527+
Попробуйте изменить запрос или выберите другую
528+
экосистему{" "}
570529
</Typography>
571530
</Box>
572531
)}
573532
</Box>
574-
575-
{/* Pagination */}
576533
{totalPages > 1 && (
577534
<Box sx={{ display: "flex", justifyContent: "center", mt: 6 }}>
578535
<Pagination
@@ -583,11 +540,7 @@ export default function ExplorePage() {
583540
size="large"
584541
showFirstButton
585542
showLastButton
586-
sx={{
587-
"& .MuiPaginationItem-root": {
588-
fontWeight: 600,
589-
},
590-
}}
543+
sx={{ "& .MuiPaginationItem-root": { fontWeight: 600 } }}
591544
/>
592545
</Box>
593546
)}
@@ -597,3 +550,24 @@ export default function ExplorePage() {
597550
</Box>
598551
);
599552
}
553+
554+
export default function ExplorePage() {
555+
return (
556+
<Suspense
557+
fallback={
558+
<Box
559+
sx={{
560+
minHeight: "100vh",
561+
display: "flex",
562+
alignItems: "center",
563+
justifyContent: "center",
564+
}}
565+
>
566+
<Typography>Загрузка...</Typography>
567+
</Box>
568+
}
569+
>
570+
<ExploreContent />
571+
</Suspense>
572+
);
573+
}

0 commit comments

Comments
 (0)