App refactoring (#257)

* add cache

* update Panel

* next app refacoring

* app style refactoring

* years refactroing
This commit is contained in:
tmshv authored and GitHub committed 2026-02-08 16:34:08 +07:00
1 parent 9ee9bce197
commit 0fd75a3b9a
10 files changed
+102 -88

No files matched your search

+10
View File
@@ -59,5 +59,15 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Cache Next.js build
uses: actions/cache@v4
with:
path: .next/cache
key: nextjs-${{ hashFiles('package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx', '**/*.css') }}
restore-keys: nextjs-${{ hashFiles('package-lock.json') }}-
- name: Disable Next.js telemetry
run: npx next telemetry disable
- name: Build - name: Build
run: npm run build run: npm run build
+3 -2
View File
@@ -1,7 +1,8 @@
import type { NextPage } from "next" import type { NextPage } from "next"
import { Wrapper } from "@/components/Wrapper" import { Wrapper } from "@/components/Wrapper"
import { NotFound } from "@/components/NotFound" import { NotFound } from "@/components/NotFound"
import Copyright from "@/components/Copyright" import { Copyright } from "@/components/Copyright"
import { yearRange } from "@/const"
import { Spacer } from "@/components/Spacer" import { Spacer } from "@/components/Spacer"
import { Box } from "@/ui/Box" import { Box } from "@/ui/Box"
@@ -11,7 +12,7 @@ const Index: NextPage = () => (
footer={( footer={(
<Box as={"footer"}> <Box as={"footer"}>
<Spacer /> <Spacer />
<Copyright /> <Copyright years={yearRange} />
<Spacer /> <Spacer />
</Box> </Box>
)} )}
+37 -41
View File
@@ -4,56 +4,52 @@ import "src/style/article.css"
import "src/style/marker/marker-table.css" import "src/style/marker/marker-table.css"
import "src/style/kazimir/video.css" import "src/style/kazimir/video.css"
import App from "next/app" import type { AppProps } from "next/app"
import Head from "next/head" import Head from "next/head"
import Script from "next/script" import Script from "next/script"
import { YMetrika } from "@/components/YMetrika" import { YMetrika } from "@/components/YMetrika"
import { ThemeColor } from "@/ui/ThemeColor" import { ThemeColor } from "@/ui/ThemeColor"
export default class MyApp extends App { export default function MyApp({ Component, pageProps }: AppProps) {
render() { return (
const { Component, pageProps } = this.props <>
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, maximum-scale=1.0" />
<meta name="referrer" content="unsafe-url" />
return ( <meta name="google-site-verification" content="8BxOTw-Q02DS2KAdcGcnIMwiPZ58DVB18XpOfRibCcg" />
<> <meta name="google-site-verification" content="A-TEemUw4hHpULUDCd6xhxfHnyRbgKtss90JGKXnSpw" />
<Head> <meta name='yandex-verification' content='643c7dac144b9dac' />
<meta charSet="utf-8" /> <meta name="msvalidate.01" content="E878301FE3E3F12D233A6B1156889601" />
<meta name="viewport" content="width=device-width, maximum-scale=1.0" /> <meta name="proculture-verification" content="79110940fd847c3cfe52392ed68f3210" />
<meta name="referrer" content="unsafe-url" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="google-site-verification" content="8BxOTw-Q02DS2KAdcGcnIMwiPZ58DVB18XpOfRibCcg" /> <link rel="icon" type="image/png" href="/static/graphics/favicon-32.png" />
<meta name="google-site-verification" content="A-TEemUw4hHpULUDCd6xhxfHnyRbgKtss90JGKXnSpw" /> <link rel="apple-touch-icon" sizes="57x57" href="/static/graphics/favicon-57.png" />
<meta name='yandex-verification' content='643c7dac144b9dac' /> <link rel="apple-touch-icon" sizes="72x72" href="/static/graphics/favicon-72.png" />
<meta name="msvalidate.01" content="E878301FE3E3F12D233A6B1156889601" /> <link rel="apple-touch-icon" sizes="114x114" href="/static/graphics/favicon-114.png" />
<meta name="proculture-verification" content="79110940fd847c3cfe52392ed68f3210" /> <link rel="apple-touch-icon" sizes="144x144" href="/static/graphics/favicon-144.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="icon" type="image/png" href="/static/graphics/favicon-32.png" /> <link
<link rel="apple-touch-icon" sizes="57x57" href="/static/graphics/favicon-57.png" /> rel="alternate"
<link rel="apple-touch-icon" sizes="72x72" href="/static/graphics/favicon-72.png" /> type="application/rss+xml"
<link rel="apple-touch-icon" sizes="114x114" href="/static/graphics/favicon-114.png" /> title="Шлиссельбургская Детская Художественная Школа"
<link rel="apple-touch-icon" sizes="144x144" href="/static/graphics/favicon-144.png" /> href="/feed.xml"
/>
<link {process.env.NEXT_PUBLIC_YMETRIKA_ACCOUNT && (
rel="alternate" <YMetrika account={Number(process.env.NEXT_PUBLIC_YMETRIKA_ACCOUNT)} />
type="application/rss+xml" )}
title="Шлиссельбургская Детская Художественная Школа"
href="/feed.xml"
/>
{process.env.NEXT_PUBLIC_YMETRIKA_ACCOUNT && ( <ThemeColor
<YMetrika account={Number(process.env.NEXT_PUBLIC_YMETRIKA_ACCOUNT)} /> color="white"
)} darkColor="hsl(240, 10%, 10%)"
/>
</Head>
<Script src="https://culturaltracking.ru/static/js/spxl.js?pixelId=27154" data-pixel-id="27154"></Script>
<ThemeColor <Component {...pageProps} />
color="white" </>
darkColor="hsl(240, 10%, 10%)" )
/>
</Head>
<Script src="https://culturaltracking.ru/static/js/spxl.js?pixelId=27154" data-pixel-id="27154"></Script>
<Component {...pageProps} />
</>
)
}
} }
+18
View File
@@ -0,0 +1,18 @@
.navigation {
padding: var(--size-s);
}
.footerLinks {
margin-bottom: var(--size-l);
}
.footerPanel {
align-items: baseline;
}
.gosuslugi {
background-color: white;
padding: 10px;
border-radius: 10px;
margin-bottom: 30px;
}
+15 -27
View File
@@ -1,3 +1,5 @@
import s from "./app.module.css"
import { Wrapper } from "../Wrapper" import { Wrapper } from "../Wrapper"
import { MenuContext } from "@/context/MenuContext" import { MenuContext } from "@/context/MenuContext"
import { Footer } from "../Footer" import { Footer } from "../Footer"
@@ -9,7 +11,8 @@ import { useMobile } from "@/hooks/useMobile"
import { Navigation } from "@/components/Navigation" import { Navigation } from "@/components/Navigation"
import { Box } from "@/ui/Box" import { Box } from "@/ui/Box"
import { AccessibilityButton } from "../AccessibilityButton" import { AccessibilityButton } from "../AccessibilityButton"
import Copyright from "@/components/Copyright" import { Copyright } from "@/components/Copyright"
import { yearRange } from "@/const"
import { Spacer } from "../Spacer" import { Spacer } from "../Spacer"
import { Author } from "../Author" import { Author } from "../Author"
import { Title } from "@/ui/Title" import { Title } from "@/ui/Title"
@@ -18,7 +21,6 @@ import { Contacts } from "../Contacts"
import dynamic from "next/dynamic" import dynamic from "next/dynamic"
import Link from "next/link" import Link from "next/link"
import Image from "next/image"
import AccessibilityPanel from "../AccessibilityPanel" import AccessibilityPanel from "../AccessibilityPanel"
import useAccessibility from "@/hooks/useAccessibility" import useAccessibility from "@/hooks/useAccessibility"
@@ -34,13 +36,10 @@ export type AppProps = {
children?: React.ReactNode children?: React.ReactNode
} }
export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props }) => { export function App({ showAuthor = false, menu, ...props }: AppProps) {
const hideBreadcrumbs = useMobile() const hideBreadcrumbs = useMobile()
const accessibility = useAccessibility() const accessibility = useAccessibility()
const router = useRouter() const router = useRouter()
const blockStyle = {
justifyContent: "center",
}
return ( return (
<MenuContext.Provider value={menu}> <MenuContext.Provider value={menu}>
@@ -51,11 +50,11 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
<AccessibilityPanel/> <AccessibilityPanel/>
)} )}
<Navigation style={{ <div className={s.navigation}>
padding: "var(--size-s)", <Navigation />
}} /> </div>
{hideBreadcrumbs || !props.breadcrumbs ? null : ( {hideBreadcrumbs || !props.breadcrumbs ? null : (
<Box style={blockStyle}> <Box center>
<Content> <Content>
<Breadcrumbs <Breadcrumbs
items={props.breadcrumbs} items={props.breadcrumbs}
@@ -68,25 +67,19 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
)} )}
footer={( footer={(
<Footer> <Footer>
<Box wrap gap={40} align={false} style={{ <Box wrap gap={40} align={false} className={s.footerLinks}>
marginBottom: "var(--size-l)",
}}>
<Panel ghost> <Panel ghost>
<Contacts /> <Contacts />
</Panel> </Panel>
<Panel ghost style={{ <Panel ghost className={s.footerPanel}>
alignItems: "baseline",
}}>
<Title level={3}> <Title level={3}>
Версия для слабовидящих Версия для слабовидящих
</Title> </Title>
<AccessibilityButton /> <AccessibilityButton />
</Panel> </Panel>
<Panel ghost style={{ <Panel ghost className={s.footerPanel}>
alignItems: "baseline",
}}>
<Title level={3}> <Title level={3}>
Анкеты Анкеты
</Title> </Title>
@@ -113,17 +106,12 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
</Panel> </Panel>
</Box> </Box>
<div style={{ <div className={s.gosuslugi}>
backgroundColor: "white",
padding: 10,
borderRadius: 10,
marginBottom: 30,
}}>
<Gosuslugi /> <Gosuslugi />
</div> </div>
<Box> <Box>
<Copyright /> <Copyright years={yearRange} />
{!showAuthor ? null : ( {!showAuthor ? null : (
<> <>
@@ -135,7 +123,7 @@ export const App: React.FC<AppProps> = ({ showAuthor = false, menu, ...props })
</Footer> </Footer>
)} )}
> >
<Box style={blockStyle}> <Box center>
<Content style={props.contentStyle}> <Content style={props.contentStyle}>
{props.children} {props.children}
</Content> </Content>
+7 -11
View File
@@ -1,13 +1,9 @@
import { state } from "@/store/config" export type CopyrightProps = {
import { useSnapshot } from "valtio" years: string
function Copyright() {
const { years } = useSnapshot(state)
const year = years.map(String).join("—")
return (
<span>©&nbsp;{year} Шлиссельбургская детская художественная школа</span>
)
} }
export default Copyright export function Copyright({ years }: CopyrightProps) {
return (
<span>©&nbsp;{years} Шлиссельбургская детская художественная школа</span>
)
}
+1
View File
@@ -0,0 +1 @@
export const yearRange = [2012, 2026].join("—")
-4
View File
@@ -1,7 +1,5 @@
import { proxy } from "valtio" import { proxy } from "valtio"
const currentYear = () => (new Date()).getFullYear()
type Contact = { type Contact = {
type: string type: string
value: string value: string
@@ -9,12 +7,10 @@ type Contact = {
} }
type State = { type State = {
years: [number, number]
contacts: Contact[] contacts: Contact[]
} }
export const state = proxy<State>({ export const state = proxy<State>({
years: [2012, currentYear()],
contacts: [ contacts: [
{ {
type: "link", type: "link",
+6
View File
@@ -20,4 +20,10 @@ describe("Panel", () => {
expect(el.className).toMatch(/ghost/) expect(el.className).toMatch(/ghost/)
expect(el.className).not.toMatch(/default/) expect(el.className).not.toMatch(/default/)
}) })
it("forwards a custom className", () => {
const { container } = render(<Panel className="custom">content</Panel>)
const el = container.firstElementChild!
expect(el.className).toContain("custom")
})
}) })
+5 -3
View File
@@ -5,18 +5,20 @@ import { Box } from "../Box"
export type PanelProps = { export type PanelProps = {
children?: React.ReactNode children?: React.ReactNode
style?: React.CSSProperties style?: React.CSSProperties
className?: string
ghost?: boolean ghost?: boolean
} }
export const Panel: React.FC<PanelProps> = ({ export function Panel({
style, style,
className,
children, children,
ghost = false, ghost = false,
}) => { }: PanelProps) {
const variant = ghost ? s.ghost : s.default const variant = ghost ? s.ghost : s.default
return ( return (
<Box vertical align={false} className={`${s.panel} ${variant}`} style={style}> <Box vertical align={false} className={`${s.panel} ${variant}${className ? ` ${className}` : ""}`} style={style}>
{children} {children}
</Box> </Box>
) )