Moved to next js ssr
This commit is contained in:
parent
4e4f798d85
commit
2184f1642c
7
.env
7
.env
@ -1,2 +1,5 @@
|
||||
#REACT_APP_HOST_URL=https://wiki-backend.dawn-of-war.pro
|
||||
REACT_APP_HOST_URL=http://localhost:8082
|
||||
NEXT_PUBLIC_HOST_URL=https://wiki-backend.dawn-of-war.pro
|
||||
#NEXT_PUBLIC_HOST_URL=http://localhost:8082
|
||||
|
||||
# Домен сайта для sitemap/robots/canonical (замените на реальный)
|
||||
NEXT_PUBLIC_SITE_URL=https://wiki.dawn-of-war.pro
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.next
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
|
||||
78
app/layout.tsx
Normal file
78
app/layout.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import '@/src/index.css';
|
||||
import '@/src/App.css';
|
||||
import '@/src/css/Unit.css';
|
||||
import '@/src/css/Building.css';
|
||||
import { ThemeProvider } from '@/src/context/ThemeContext';
|
||||
import AppShell from '@/components/AppShell';
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://dow-wiki.example.com';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(siteUrl),
|
||||
title: {
|
||||
default: 'Dawn of War Wiki — юниты, расы и здания всех модов',
|
||||
template: '%s — Dawn of War Wiki',
|
||||
},
|
||||
description:
|
||||
'Dawn of War wiki. Unification mod wiki. Unification mod unit stats. Supported all popular mods.',
|
||||
manifest: '/manifest.json',
|
||||
applicationName: 'Dawn of War Wiki',
|
||||
verification: {
|
||||
google: 'Q4XMn2UDpL2xmK7iBfaWQpVN8EPy8oGxN-7HqSKapb0',
|
||||
},
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
siteName: 'Dawn of War Wiki',
|
||||
title: 'Dawn of War Wiki — юниты, расы и здания всех модов',
|
||||
description:
|
||||
'Dawn of War wiki. Unification mod wiki. Unification mod unit stats. Supported all popular mods.',
|
||||
images: ['/logo512.png'],
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: '#000000',
|
||||
width: 'device-width',
|
||||
initialScale: 1,
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
{/* Yandex.Metrika */}
|
||||
<script
|
||||
type="text/javascript"
|
||||
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;}}k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})(window,document,\"script\",\"https://mc.yandex.ru/metrika/tag.js\",\"ym\");ym(99502794,\"init\",{clickmap:true,trackLinks:true,accurateTrackBounce:true,webvisor:true});",
|
||||
}}
|
||||
/>
|
||||
<noscript>
|
||||
<div>
|
||||
<img
|
||||
src="https://mc.yandex.ru/watch/99502794"
|
||||
style={{ position: 'absolute', left: -9999 }}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</noscript>
|
||||
<ThemeProvider>
|
||||
<AppShell>{children}</AppShell>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
33
app/mod/[modId]/page.tsx
Normal file
33
app/mod/[modId]/page.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import type { Metadata } from 'next';
|
||||
import ModPageClient from '@/components/pages/ModPageClient';
|
||||
import { getMod } from '@/lib/api-server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface Props {
|
||||
params: { modId: string };
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const mod = await getMod(params.modId);
|
||||
if (!mod) {
|
||||
return { title: 'Mod' };
|
||||
}
|
||||
const title = `${mod.name} (${mod.version})`;
|
||||
const description = `Dawn of War wiki. ${mod.name} mod. Units, races, buildings and stats.`;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: `/mod/${params.modId}`,
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function ModPage() {
|
||||
return <ModPageClient />;
|
||||
}
|
||||
37
app/mod/[modId]/race/[raceId]/building/[buildingId]/page.tsx
Normal file
37
app/mod/[modId]/race/[raceId]/building/[buildingId]/page.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import type { Metadata } from 'next';
|
||||
import BuildingPageClient from '@/components/pages/BuildingPageClient';
|
||||
import { getBuilding, getMod } from '@/lib/api-server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface Props {
|
||||
params: { modId: string; raceId: string; buildingId: string };
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const [mod, building] = await Promise.all([getMod(params.modId), getBuilding(params.buildingId)]);
|
||||
if (!mod || !building) {
|
||||
return { title: 'Building' };
|
||||
}
|
||||
let buildingName = building.name;
|
||||
if (!buildingName) {
|
||||
buildingName = building.filename.replaceAll('_', ' ').replace('.rgd', '');
|
||||
}
|
||||
const title = `${buildingName} — ${mod.name}`;
|
||||
const description = `${buildingName} building in ${mod.name} mod. Stats, production, researches.`;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: `/mod/${params.modId}/race/${params.raceId}/building/${params.buildingId}`,
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function BuildingPage() {
|
||||
return <BuildingPageClient />;
|
||||
}
|
||||
33
app/mod/[modId]/race/[raceId]/page.tsx
Normal file
33
app/mod/[modId]/race/[raceId]/page.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import type { Metadata } from 'next';
|
||||
import RacePageClient from '@/components/pages/RacePageClient';
|
||||
import { getMod, getRace } from '@/lib/api-server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface Props {
|
||||
params: { modId: string; raceId: string };
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const [mod, race] = await Promise.all([getMod(params.modId), getRace(params.raceId)]);
|
||||
if (!mod || !race) {
|
||||
return { title: 'Race' };
|
||||
}
|
||||
const title = `${race.name} — ${mod.name}`;
|
||||
const description = `${race.name} race in ${mod.name} mod. Units, buildings and stats.`;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: `/mod/${params.modId}/race/${params.raceId}`,
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function RacePage() {
|
||||
return <RacePageClient />;
|
||||
}
|
||||
35
app/mod/[modId]/race/[raceId]/unit/[unitId]/page.tsx
Normal file
35
app/mod/[modId]/race/[raceId]/unit/[unitId]/page.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import type { Metadata } from 'next';
|
||||
import UnitPageClient from '@/components/pages/UnitPageClient';
|
||||
import { getMod, getUnit } from '@/lib/api-server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
interface Props {
|
||||
params: { modId: string; raceId: string; unitId: string };
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const [mod, unit] = await Promise.all([getMod(params.modId), getUnit(params.unitId)]);
|
||||
if (!mod || !unit) {
|
||||
return { title: 'Unit' };
|
||||
}
|
||||
const title = `${unit.name} — ${mod.name}`;
|
||||
const description = unit.description
|
||||
? `${unit.name} unit in ${mod.name} mod. ${unit.description}`
|
||||
: `${unit.name} unit in ${mod.name} mod. Stats, weapons, abilities.`;
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: {
|
||||
canonical: `/mod/${params.modId}/race/${params.raceId}/unit/${params.unitId}`,
|
||||
},
|
||||
openGraph: {
|
||||
title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default function UnitPage() {
|
||||
return <UnitPageClient />;
|
||||
}
|
||||
21
app/page.tsx
Normal file
21
app/page.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import type { Metadata } from 'next';
|
||||
import ModsPageClient from '@/components/pages/ModsPageClient';
|
||||
import { getMods } from '@/lib/api-server';
|
||||
import { IMod } from '@/src/types/Imod';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Dawn of War Wiki — юниты, расы и здания всех модов',
|
||||
description:
|
||||
'Dawn of War wiki. Unification mod wiki. Unification mod unit stats. Supported all popular mods.',
|
||||
alternates: {
|
||||
canonical: '/',
|
||||
},
|
||||
};
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default async function HomePage() {
|
||||
const mods = (await getMods()) ?? [];
|
||||
const visibleMods = mods.filter(mod => !mod.isHide);
|
||||
return <ModsPageClient initialMods={visibleMods} />;
|
||||
}
|
||||
12
app/robots.ts
Normal file
12
app/robots.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { SITE_URL } from '@/lib/api-server';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
return {
|
||||
rules: {
|
||||
userAgent: '*',
|
||||
allow: '/',
|
||||
},
|
||||
sitemap: `${SITE_URL}/sitemap.xml`,
|
||||
};
|
||||
}
|
||||
78
app/sitemap.ts
Normal file
78
app/sitemap.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import type { MetadataRoute } from 'next';
|
||||
import {
|
||||
getBuildingsForMod,
|
||||
getMods,
|
||||
getUnitsForMod,
|
||||
SITE_URL,
|
||||
} from '@/lib/api-server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const mods = (await getMods()) ?? [];
|
||||
|
||||
const modEntries: MetadataRoute.Sitemap = mods.map((mod) => ({
|
||||
url: `${SITE_URL}/mod/${mod.id}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.8,
|
||||
}));
|
||||
|
||||
const entries: MetadataRoute.Sitemap = [...modEntries];
|
||||
|
||||
// Gather races/units/buildings per mod (limit parallelism)
|
||||
const details = await Promise.all(
|
||||
mods.slice(0, 20).map(async (mod) => {
|
||||
const [units, buildings] = await Promise.all([
|
||||
getUnitsForMod(mod.id),
|
||||
getBuildingsForMod(mod.id),
|
||||
]);
|
||||
return { mod, units, buildings };
|
||||
})
|
||||
);
|
||||
|
||||
for (const { mod, units, buildings } of details) {
|
||||
const raceIds = new Set<string>();
|
||||
for (const unitGroup of units ?? []) raceIds.add(unitGroup.race.id);
|
||||
for (const buildingGroup of buildings ?? []) raceIds.add(buildingGroup.race.id);
|
||||
|
||||
for (const raceId of raceIds) {
|
||||
entries.push({
|
||||
url: `${SITE_URL}/mod/${mod.id}/race/${raceId}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.7,
|
||||
});
|
||||
}
|
||||
for (const unitGroup of units ?? []) {
|
||||
for (const unit of [...unitGroup.infantry, ...unitGroup.support, ...unitGroup.tech]) {
|
||||
entries.push({
|
||||
url: `${SITE_URL}/mod/${mod.id}/race/${unitGroup.race.id}/unit/${unit.id}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.5,
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const buildingGroup of buildings ?? []) {
|
||||
for (const building of [...buildingGroup.buildings, ...buildingGroup.buildingsAdvanced]) {
|
||||
entries.push({
|
||||
url: `${SITE_URL}/mod/${mod.id}/race/${buildingGroup.race.id}/building/${building.id}`,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.5,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
url: SITE_URL,
|
||||
lastModified: new Date(),
|
||||
changeFrequency: 'daily',
|
||||
priority: 1,
|
||||
},
|
||||
...entries,
|
||||
];
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import './App.css';
|
||||
import React from "react";
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import {
|
||||
AppBar,
|
||||
Box,
|
||||
@ -7,11 +7,10 @@ import {
|
||||
IconButton,
|
||||
Toolbar,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useNavigate, BrowserRouter } from "react-router-dom";
|
||||
import { MyRoutes } from "./Routes";
|
||||
} from '@mui/material';
|
||||
import Link from 'next/link';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { ThemeProvider as CustomThemeProvider, useThemeContext } from './context/ThemeContext';
|
||||
import { useThemeContext } from '@/src/context/ThemeContext';
|
||||
import DarkModeIcon from '@mui/icons-material/DarkMode';
|
||||
import LightModeIcon from '@mui/icons-material/LightMode';
|
||||
|
||||
@ -58,25 +57,30 @@ const ThemeToggleLight = styled(IconButton)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
function AppBarContent() {
|
||||
const LogoLink = styled(Link)({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
textDecoration: 'none',
|
||||
color: 'inherit',
|
||||
});
|
||||
|
||||
export default function AppShell({ children }: { children: React.ReactNode }) {
|
||||
const { mode, toggleTheme } = useThemeContext();
|
||||
const navigate = useNavigate();
|
||||
const isDark = mode === 'dark';
|
||||
|
||||
const handleLogoClick = () => navigate('/');
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ minHeight: '100vh' }}>
|
||||
{isDark ? (
|
||||
<StyledAppBar position="sticky">
|
||||
<Container maxWidth="lg" sx={{ px: { xs: 1, sm: 3, md: 3 } }}>
|
||||
<Toolbar disableGutters sx={{ justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<img src="/images/dowdelogo.png" alt="logo" style={{ height: 32, cursor: 'pointer' }} onClick={handleLogoClick}/>
|
||||
<LogoText variant="h6" sx={{ textDecoration: 'none', cursor: 'pointer' }} onClick={handleLogoClick}>
|
||||
<LogoLink href="/">
|
||||
<img src="/images/dowdelogo.png" alt="logo" style={{ height: 32, cursor: 'pointer' }} />
|
||||
<LogoText variant="h6">
|
||||
Wiki
|
||||
</LogoText>
|
||||
</Box>
|
||||
</LogoLink>
|
||||
<ThemeToggle onClick={toggleTheme} size="large">
|
||||
<LightModeIcon />
|
||||
</ThemeToggle>
|
||||
@ -87,12 +91,12 @@ function AppBarContent() {
|
||||
<StyledAppBarLight position="sticky">
|
||||
<Container maxWidth="lg" sx={{ px: { xs: 1, sm: 3, md: 3 } }}>
|
||||
<Toolbar disableGutters sx={{ justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<img src="/images/dowdelogo.png" alt="logo" style={{ height: 32, cursor: 'pointer' }} onClick={handleLogoClick}/>
|
||||
<LogoText variant="h6" sx={{ textDecoration: 'none', cursor: 'pointer' }} onClick={handleLogoClick}>
|
||||
<LogoLink href="/">
|
||||
<img src="/images/dowdelogo.png" alt="logo" style={{ height: 32, cursor: 'pointer' }} />
|
||||
<LogoText variant="h6">
|
||||
Wiki
|
||||
</LogoText>
|
||||
</Box>
|
||||
</LogoLink>
|
||||
<ThemeToggleLight onClick={toggleTheme} size="large">
|
||||
<DarkModeIcon sx={{ color: '#000000' }} />
|
||||
</ThemeToggleLight>
|
||||
@ -100,23 +104,9 @@ function AppBarContent() {
|
||||
</Container>
|
||||
</StyledAppBarLight>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<CustomThemeProvider>
|
||||
<Box sx={{ minHeight: '100vh' }}>
|
||||
<BrowserRouter>
|
||||
<AppBarContent />
|
||||
<Container maxWidth="lg" sx={{ py: 4, px: { xs: 1, sm: 3, md: 3 } }}>
|
||||
<MyRoutes />
|
||||
{children}
|
||||
</Container>
|
||||
</BrowserRouter>
|
||||
</Box>
|
||||
</CustomThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
16
components/pages/BuildingPageClient.tsx
Normal file
16
components/pages/BuildingPageClient.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, LinearProgress } from '@mui/material';
|
||||
|
||||
const BuildingPageLegacy = dynamic(() => import('@/src/legacy-pages/BuildingPage'), {
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||
<LinearProgress sx={{ width: '200px' }} />
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
|
||||
export default function BuildingPageClient() {
|
||||
return <BuildingPageLegacy />;
|
||||
}
|
||||
16
components/pages/ModPageClient.tsx
Normal file
16
components/pages/ModPageClient.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, LinearProgress } from '@mui/material';
|
||||
|
||||
const ModPageLegacy = dynamic(() => import('@/src/legacy-pages/ModPage'), {
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||
<LinearProgress sx={{ width: '200px' }} />
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
|
||||
export default function ModPageClient() {
|
||||
return <ModPageLegacy />;
|
||||
}
|
||||
14
components/pages/ModsPageClient.tsx
Normal file
14
components/pages/ModsPageClient.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, LinearProgress } from '@mui/material';
|
||||
import { IMod } from '@/src/types/Imod';
|
||||
|
||||
const ModsPageLegacy = dynamic(() => import('@/src/legacy-pages/ModsPage'));
|
||||
|
||||
interface ModsPageClientProps {
|
||||
initialMods: IMod[];
|
||||
}
|
||||
|
||||
export default function ModsPageClient({ initialMods }: ModsPageClientProps) {
|
||||
return <ModsPageLegacy initialMods={initialMods} />;
|
||||
}
|
||||
16
components/pages/RacePageClient.tsx
Normal file
16
components/pages/RacePageClient.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, LinearProgress } from '@mui/material';
|
||||
|
||||
const RacePageLegacy = dynamic(() => import('@/src/legacy-pages/RacePageFast'), {
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||
<LinearProgress sx={{ width: '200px' }} />
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
|
||||
export default function RacePageClient() {
|
||||
return <RacePageLegacy />;
|
||||
}
|
||||
16
components/pages/UnitPageClient.tsx
Normal file
16
components/pages/UnitPageClient.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, LinearProgress } from '@mui/material';
|
||||
|
||||
const UnitPageLegacy = dynamic(() => import('@/src/legacy-pages/UnitPage'), {
|
||||
ssr: false,
|
||||
loading: () => (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', py: 8 }}>
|
||||
<LinearProgress sx={{ width: '200px' }} />
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
|
||||
export default function UnitPageClient() {
|
||||
return <UnitPageLegacy />;
|
||||
}
|
||||
49
lib/api-server.ts
Normal file
49
lib/api-server.ts
Normal file
@ -0,0 +1,49 @@
|
||||
// Server-side API helpers for metadata generation.
|
||||
// Runs only on the server (Node.js) — uses NEXT_PUBLIC_HOST_URL which is inlined at build time.
|
||||
|
||||
import { IMod } from '@/src/types/Imod';
|
||||
|
||||
const API = process.env.NEXT_PUBLIC_HOST_URL || '';
|
||||
export const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://dow-wiki.example.com';
|
||||
|
||||
async function fetchJson<T>(url: string): Promise<T | null> {
|
||||
try {
|
||||
const res = await fetch(url, { cache: 'no-store' });
|
||||
if (!res.ok) return null;
|
||||
return (await res.json()) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getMod(modId: string) {
|
||||
return fetchJson<{ name: string; version: string }>(`${API}/api/v1/mods/${modId}`);
|
||||
}
|
||||
|
||||
export async function getRace(raceId: string) {
|
||||
return fetchJson<{ name: string }>(`${API}/api/v1/races/${raceId}`);
|
||||
}
|
||||
|
||||
export async function getUnit(unitId: string) {
|
||||
return fetchJson<{ name: string; description: string }>(`${API}/api/v1/units/${unitId}`);
|
||||
}
|
||||
|
||||
export async function getBuilding(buildingId: string) {
|
||||
return fetchJson<{ name: string; filename: string }>(`${API}/api/v1/buildings/${buildingId}`);
|
||||
}
|
||||
|
||||
export async function getMods() {
|
||||
return fetchJson<IMod[]>(`${API}/api/v1/mods`);
|
||||
}
|
||||
|
||||
export async function getRacesForMod(modId: string | number) {
|
||||
return fetchJson<Array<{ race: { id: string; name: string } }>>(`${API}/api/v1/units/mod/${modId}`);
|
||||
}
|
||||
|
||||
export async function getUnitsForMod(modId: string | number) {
|
||||
return fetchJson<Array<{ race: { id: string }; infantry: Array<{ id: number }>; support: Array<{ id: number }>; tech: Array<{ id: number }> }>>(`${API}/api/v1/units/mod/${modId}`);
|
||||
}
|
||||
|
||||
export async function getBuildingsForMod(modId: string | number) {
|
||||
return fetchJson<Array<{ race: { id: string }; buildings: Array<{ id: number }>; buildingsAdvanced: Array<{ id: number }> }>>(`${API}/api/v1/buildings/mod/${modId}`);
|
||||
}
|
||||
5
next-env.d.ts
vendored
Normal file
5
next-env.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
6
next.config.mjs
Normal file
6
next.config.mjs
Normal file
@ -0,0 +1,6 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
17872
package-lock.json
generated
17872
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@ -1,43 +1,26 @@
|
||||
{
|
||||
"name": "dow-wiki-frontend",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^6.3.1",
|
||||
"@mui/material": "^6.3.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"next": "^14.2.23",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.28.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache, must-revalidate" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Dawn of War wiki. Unification mod wiki. Unification mod unit stats. Supported all popular mods."
|
||||
/>
|
||||
<meta name="google-site-verification" content="Q4XMn2UDpL2xmK7iBfaWQpVN8EPy8oGxN-7HqSKapb0" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Dawn of War Wiki</title>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Yandex.Metrika counter --> <script type="text/javascript" > (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; }} k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)}) (window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym"); ym(99502794, "init", { clickmap:true, trackLinks:true, accurateTrackBounce:true, webvisor:true }); </script> <noscript><div><img src="https://mc.yandex.ru/watch/99502794" style="position:absolute; left:-9999px;" alt="" /></div></noscript> <!-- /Yandex.Metrika counter -->
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<script>
|
||||
(function () {
|
||||
var VERSION = 'v1.0.1';
|
||||
var stored = localStorage.getItem('app_version');
|
||||
if (stored !== VERSION) {
|
||||
localStorage.setItem('app_version', VERSION);
|
||||
if ('caches' in window) {
|
||||
caches.keys().then(function (names) {
|
||||
names.forEach(function (name) { caches.delete(name); });
|
||||
});
|
||||
}
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.getRegistrations().then(function (regs) {
|
||||
regs.forEach(function (r) { r.unregister(); });
|
||||
});
|
||||
}
|
||||
}
|
||||
// bfcache: если страница восстановлена из back-forward cache — перезагружаем
|
||||
window.addEventListener('pageshow', function (event) {
|
||||
if (event.persisted) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"short_name": "DoW Wiki",
|
||||
"name": "Dawn of War Wiki",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
import {Routes, Route} from "react-router-dom";
|
||||
import ModsPage from "./pages/ModsPage";
|
||||
import ModPage from "./pages/ModPage";
|
||||
import RacePageFast from "./pages/RacePageFast";
|
||||
import UnitPage from "./pages/UnitPage";
|
||||
import BuildingPage from "./pages/BuildingPage";
|
||||
|
||||
export const MyRoutes = () => {
|
||||
|
||||
return(
|
||||
<Routes>
|
||||
<Route path="/" element={<ModsPage/>}/>
|
||||
<Route path="/mod/:modId" element={<ModPage/>}/>
|
||||
<Route path="/mod/:modId/race/:raceId" element={<RacePageFast/>}/>
|
||||
<Route path="/mod/:modId/race/:raceId/unit/:unitId" element={<UnitPage/>}/>
|
||||
<Route path="/mod/:modId/race/:raceId/building/:buildingId" element={<BuildingPage/>}/>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
@ -235,7 +235,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race
|
||||
{ label: <ArmorType name={ArmorTypeNames.BuildingHigh}/>, value: () => isAbilityApplyTo(ArmorTypeNames.BuildingHigh) },
|
||||
];
|
||||
|
||||
const chunks = [];
|
||||
const chunks: any[] = [];
|
||||
for (let i = 0; i < items.length; i += columnsPerRow) {
|
||||
chunks.push(items.slice(i, i + columnsPerRow));
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi
|
||||
{ label: <ArmorType name="Morale damage"/>, value: () => getMoraleDamage() },
|
||||
];
|
||||
|
||||
const chunks = [];
|
||||
const chunks: any[] = [];
|
||||
for (let i = 0; i < items.length; i += columnsPerRow) {
|
||||
chunks.push(items.slice(i, i + columnsPerRow));
|
||||
}
|
||||
|
||||
@ -277,7 +277,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,
|
||||
{ label: <ArmorType name="Morale damage"/>, value: () => getMoraleDamage() },
|
||||
];
|
||||
|
||||
const chunks = [];
|
||||
const chunks: any[] = [];
|
||||
for (let i = 0; i < items.length; i += columnsPerRow) {
|
||||
chunks.push(items.slice(i, i + columnsPerRow));
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
'use client';
|
||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
@ -14,10 +15,14 @@ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||
|
||||
|
||||
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [mode, setMode] = useState<ThemeMode>(() => {
|
||||
const [mode, setMode] = useState<ThemeMode>('dark');
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem('theme');
|
||||
return (saved === 'light' ? 'light' : 'dark') as ThemeMode;
|
||||
});
|
||||
if (saved === 'light') {
|
||||
setMode('light');
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('theme', mode);
|
||||
|
||||
@ -2,15 +2,15 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
export const UserUrl = process.env.REACT_APP_HOST_URL + '/api/v1/user';
|
||||
export const WeaponUrl = process.env.REACT_APP_HOST_URL + '/api/v1/weapon';
|
||||
export const AbilityUrl = process.env.REACT_APP_HOST_URL + '/api/v1/ability';
|
||||
export const ResearchUrl = process.env.REACT_APP_HOST_URL + '/api/v1/research';
|
||||
export const AvailableMods = process.env.REACT_APP_HOST_URL + '/api/v1/mods';
|
||||
export const AvailableRacesPart = process.env.REACT_APP_HOST_URL + '/api/v1/races';
|
||||
export const AvailableUnits = process.env.REACT_APP_HOST_URL + '/api/v1/units';
|
||||
export const AvailableBuildings = process.env.REACT_APP_HOST_URL + '/api/v1/buildings';
|
||||
export const IconUrl = process.env.REACT_APP_HOST_URL + '/api/v1/grapics/icon/';
|
||||
export const UserUrl = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/user';
|
||||
export const WeaponUrl = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/weapon';
|
||||
export const AbilityUrl = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/ability';
|
||||
export const ResearchUrl = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/research';
|
||||
export const AvailableMods = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/mods';
|
||||
export const AvailableRacesPart = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/races';
|
||||
export const AvailableUnits = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/units';
|
||||
export const AvailableBuildings = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/buildings';
|
||||
export const IconUrl = process.env.NEXT_PUBLIC_HOST_URL + '/api/v1/grapics/icon/';
|
||||
|
||||
export function withTheme(Component) {
|
||||
return function WithTheme(props) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
'use client';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
export function withRouter(Children) {
|
||||
return (props) => {
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
|
||||
@ -118,8 +118,6 @@ function Unit (unit: IUnitShort, modId: number, raceId: String, theme: Theme) {
|
||||
|
||||
function Building(building: IBuilding, mod: IMod, theme: Theme) {
|
||||
|
||||
document.title = building.name + " — " + mod.name
|
||||
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
|
||||
let mapBuildingWeapons: Map<number, Map<number, IShortWeapon>> = new Map();
|
||||
@ -66,7 +66,6 @@ class ModPage extends React.Component<any, ModPageState> {
|
||||
}
|
||||
|
||||
if (this.state != null && this.state.mod != null) {
|
||||
document.title = this.state.mod.name + " — ModWiki";
|
||||
const isDark = this.props.theme.palette.mode === 'dark';
|
||||
|
||||
return (
|
||||
@ -1,7 +1,4 @@
|
||||
import {AvailableMods} from "../core/api";
|
||||
import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { IMod } from "../types/Imod";
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
@ -16,30 +13,11 @@ import {
|
||||
useTheme,
|
||||
Paper,
|
||||
LinearProgress,
|
||||
Link,
|
||||
} from "@mui/material";
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { IMod } from "../types/Imod";
|
||||
|
||||
// Styled components
|
||||
const PageHeader = styled(Paper)(({ theme }) => ({
|
||||
padding: theme.spacing(6, 4),
|
||||
marginBottom: theme.spacing(6),
|
||||
background: theme.palette.mode === 'dark'
|
||||
? 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)'
|
||||
: 'linear-gradient(135deg, #ffffff 0%, #f0f0f0 50%, #e0e0e0 100%)',
|
||||
borderRadius: '16px',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'radial-gradient(circle at 30% 50%, rgba(255, 255, 255, 0.05) 0%, #dee2e6 50%)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
const ModCard = styled(Card)(({ theme }) => ({
|
||||
height: '100%',
|
||||
@ -75,7 +53,7 @@ const ModCard = styled(Card)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const VersionLink = styled(NavLink)(({ theme }) => ({
|
||||
const VersionLink = styled(Link)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: theme.spacing(1.5, 2),
|
||||
@ -214,7 +192,7 @@ function Mods({ mods }: ModsProps) {
|
||||
</Box>
|
||||
|
||||
{sameMods.filter(m => !m.isBeta).map(mod => (
|
||||
<VersionLink key={mod.id} to={"/mod/" + mod.id} state={mod.id}>
|
||||
<VersionLink key={mod.id} href={"/mod/" + mod.id}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
||||
<VersionText variant="body2">
|
||||
Version {mod.version}
|
||||
@ -232,7 +210,7 @@ function Mods({ mods }: ModsProps) {
|
||||
Beta versions:
|
||||
</BetaVersionsLabel>
|
||||
{betaVersion && (
|
||||
<VersionLink to={"/mod/" + betaVersion.id} state={betaVersion.id}>
|
||||
<VersionLink href={"/mod/" + betaVersion.id}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: '100%' }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
Version {betaVersion.version} (Beta)
|
||||
@ -251,9 +229,7 @@ function Mods({ mods }: ModsProps) {
|
||||
<Button
|
||||
size="medium"
|
||||
variant="outlined"
|
||||
component={NavLink}
|
||||
to={"/mod/" + latest?.id}
|
||||
state={latest?.id}
|
||||
href={"/mod/" + latest?.id}
|
||||
sx={{
|
||||
width: '100%',
|
||||
color: theme.palette.mode === 'dark' ? '#FFD700' : '#1976d2',
|
||||
@ -296,13 +272,17 @@ interface ModsPageState {
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
class ModsPage extends React.Component<any, ModsPageState> {
|
||||
interface ModsPageProps {
|
||||
initialMods?: IMod[];
|
||||
}
|
||||
|
||||
constructor({props}: { props: any }) {
|
||||
class ModsPage extends React.Component<ModsPageProps, ModsPageState> {
|
||||
|
||||
constructor(props: ModsPageProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mods: [],
|
||||
loading: true
|
||||
mods: props.initialMods ?? [],
|
||||
loading: false
|
||||
};
|
||||
}
|
||||
|
||||
@ -323,18 +303,6 @@ class ModsPage extends React.Component<any, ModsPageState> {
|
||||
console.error('Cache clear failed:', e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(AvailableMods);
|
||||
const data: IMod[] = await response.json();
|
||||
this.setState({
|
||||
mods: data.filter(mod => !mod.isHide),
|
||||
loading: false
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error loading mods:', error);
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -184,7 +184,7 @@ class Units extends React.Component<UnitsProps & { theme: Theme }, UnitsState> {
|
||||
if (this.state && this.state.units) {
|
||||
|
||||
if(this.state.buildings?.race.name !== undefined){
|
||||
document.title = this.state.buildings?.race.name
|
||||
// title managed by Helmet in parent component
|
||||
}
|
||||
|
||||
const accordionSx = isDark ? {
|
||||
@ -270,8 +270,6 @@ class RacePageFast extends React.Component<any, RacePageState> {
|
||||
mod: modData
|
||||
});
|
||||
|
||||
document.title = `${modData.name} (${modData.version})`;
|
||||
|
||||
const response = await fetch(AvailableRacesPart + "/" + this.props.match.params.raceId);
|
||||
const racesData: Irace = await response.json();
|
||||
|
||||
@ -99,8 +99,6 @@ interface UintPageState {
|
||||
|
||||
function Unit(unit: IUnit, mod: IMod, theme: Theme) {
|
||||
|
||||
document.title = unit.name + " — " + mod.name
|
||||
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
|
||||
const morale = (unit.moraleMax !== null) ? <span>
|
||||
@ -1,26 +1,42 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES6",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"target": "ES2017",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"strictNullChecks": false,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user