Normalize interface naming — remove I prefix convention (#255)
* update pr workflow * interface to types * use consistent import * import type syntax * test plan note
This commit is contained in:
1 parent
7ab9147183
commit
9ee9bce197
20 files changed
+130
-100
No files matched your search
@@ -4,39 +4,60 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Image Without Push
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Build image (no push)
|
||||
uses: docker/build-push-action@v5
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
context: ./
|
||||
push: false # Do NOT push
|
||||
platforms: linux/amd64
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Move cache
|
||||
if: steps.buildx.outcome == 'success'
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
||||
build:
|
||||
name: Build
|
||||
needs: [lint, test]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
@@ -20,13 +20,13 @@ Node 22 required (see `mise.toml`). Deployed on Vercel.
|
||||
|
||||
## Git Workflow
|
||||
|
||||
Feature branches are named `issue-XXX` where `XXX` is the related GitHub issue number.
|
||||
Feature branches are named `issue-XXX` where `XXX` is the related GitHub issue number. PR descriptions should not include a test plan section.
|
||||
|
||||
## CI/CD
|
||||
|
||||
- `Dockerfile` — multi-stage Alpine build with standalone Next.js output, pushed to GHCR
|
||||
- `.github/workflows/build-app.yaml` — builds and pushes Docker image on push to master
|
||||
- `.github/workflows/test-app.yml` — builds Docker image (no push) on PRs
|
||||
- `.github/workflows/test-app.yml` — runs lint, tests, and build on PRs (build runs only after lint and tests pass)
|
||||
|
||||
## Code Style
|
||||
|
||||
@@ -75,7 +75,14 @@ CSS Modules (`.module.css`) for component-scoped styles. Global theme via CSS cu
|
||||
|
||||
## TypeScript
|
||||
|
||||
Path alias: `@/*` → `src/*`. Strict mode enabled. Target ES6. `moduleDetection: "force"` in tsconfig (required because Next.js auto-generates `.next/types/validator.ts` without exports, which breaks `isolatedModules`). Both `@/` and `src/` import styles are used in the codebase — prefer `@/` for new code.
|
||||
Path alias: `@/*` → `src/*`. Strict mode enabled. Target ES6. `moduleDetection: "force"` in tsconfig (required because Next.js auto-generates `.next/types/validator.ts` without exports, which breaks `verbatimModuleSyntax`). Use `@/` path alias for all local imports, not `src/`. Legacy `src/` imports exist in the codebase but should not be used in new or modified code.
|
||||
|
||||
`verbatimModuleSyntax` is enabled — use `import type` for type-only imports. When importing both values and types from the same module, use separate `import` and `import type` statements:
|
||||
|
||||
```ts
|
||||
import { createPage } from "@/remote/factory"
|
||||
import type { Page } from "@/types"
|
||||
```
|
||||
|
||||
## Accessibility
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { NextPage } from "next"
|
||||
import type { NextPage } from "next"
|
||||
import { Wrapper } from "@/components/Wrapper"
|
||||
import { NotFound } from "@/components/NotFound"
|
||||
import Copyright from "@/components/Copyright"
|
||||
|
||||
+10
-10
@@ -1,11 +1,11 @@
|
||||
import Head from "next/head"
|
||||
import { App } from "src/components/App"
|
||||
import { Page } from "src/components/Page"
|
||||
import { App } from "@/components/App"
|
||||
import { Page } from "@/components/Page"
|
||||
import { NextSeo } from "next-seo"
|
||||
import { MetaBuilder } from "src/lib/meta"
|
||||
import { GetStaticProps, NextPage } from "next"
|
||||
import { IBreadcumbsPart, IMenu, IMeta, ITag, Sign, Token } from "@/types"
|
||||
import { Html } from "src/components/Html"
|
||||
import { MetaBuilder } from "@/lib/meta"
|
||||
import type { GetStaticProps, NextPage } from "next"
|
||||
import type { BreadcrumbPart, MenuItem, Meta, Tag, Sign, Token } from "@/types"
|
||||
import { Html } from "@/components/Html"
|
||||
import { Youtube } from "@/components/Youtube"
|
||||
import { paramsToSlug } from "@/remote/lib"
|
||||
import { PageGrid } from "@/components/PageGrid"
|
||||
@@ -17,11 +17,11 @@ import { getMenu, getPageBySlug, getUrls } from "@/remote/api"
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
tags: ITag[]
|
||||
tags: Tag[]
|
||||
date: string
|
||||
breadcrumb: IBreadcumbsPart[]
|
||||
meta?: IMeta
|
||||
menu: IMenu[],
|
||||
breadcrumb: BreadcrumbPart[]
|
||||
meta?: Meta
|
||||
menu: MenuItem[],
|
||||
tokens: Token[]
|
||||
documentSignature: Sign
|
||||
}
|
||||
|
||||
+8
-8
@@ -1,18 +1,18 @@
|
||||
import Head from "next/head"
|
||||
import { App } from "src/components/App"
|
||||
import { App } from "@/components/App"
|
||||
import { NextSeo } from "next-seo"
|
||||
import { PageGrid } from "src/components/PageGrid"
|
||||
import { HudozkaTitle } from "src/components/HudozkaTitle"
|
||||
import { MetaBuilder } from "src/lib/meta"
|
||||
import { GetStaticProps, NextPage } from "next"
|
||||
import { IMenu, IMeta, PageCardDto } from "src/types"
|
||||
import { PageGrid } from "@/components/PageGrid"
|
||||
import { HudozkaTitle } from "@/components/HudozkaTitle"
|
||||
import { MetaBuilder } from "@/lib/meta"
|
||||
import type { GetStaticProps, NextPage } from "next"
|
||||
import type { MenuItem, Meta, PageCardDto } from "@/types"
|
||||
import { useMobile } from "@/hooks/useMobile"
|
||||
import { getHomeCards, getMenu } from "@/remote/api"
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
meta: IMeta
|
||||
menu: IMenu[]
|
||||
meta: Meta
|
||||
menu: MenuItem[]
|
||||
items: PageCardDto[]
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { MenuContext } from "@/context/MenuContext"
|
||||
import { Footer } from "../Footer"
|
||||
import { Content } from "../Content"
|
||||
import { Breadcrumbs } from "../Breadcrumbs"
|
||||
import { IBreadcumbsPart, IMenu } from "src/types"
|
||||
import type { BreadcrumbPart, MenuItem } from "@/types"
|
||||
import { useRouter } from "next/router"
|
||||
import { useMobile } from "src/hooks/useMobile"
|
||||
import { useMobile } from "@/hooks/useMobile"
|
||||
import { Navigation } from "@/components/Navigation"
|
||||
import { Box } from "@/ui/Box"
|
||||
import { AccessibilityButton } from "../AccessibilityButton"
|
||||
@@ -29,8 +29,8 @@ const Gosuslugi = dynamic(import("../Gosuslugi").then(module => module.Gosuslugi
|
||||
export type AppProps = {
|
||||
showAuthor?: boolean
|
||||
contentStyle?: React.CSSProperties
|
||||
breadcrumbs?: IBreadcumbsPart[]
|
||||
menu: IMenu[]
|
||||
breadcrumbs?: BreadcrumbPart[]
|
||||
menu: MenuItem[]
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { IBreadcumbsPart } from "src/types"
|
||||
import type { BreadcrumbPart } from "@/types"
|
||||
import { Button } from "@/ui/Button"
|
||||
import { insertBetween } from "src/lib/array"
|
||||
import { insertBetween } from "@/lib/array"
|
||||
|
||||
export type BreadcrumbsProps = {
|
||||
style?: React.CSSProperties
|
||||
items: IBreadcumbsPart[]
|
||||
items: BreadcrumbPart[]
|
||||
path: string
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { FileDefinition } from "src/types"
|
||||
import type { FileDefinition } from "@/types"
|
||||
import { ext, size } from "src/lib/file"
|
||||
import Link from "next/link"
|
||||
import Image from "next/image"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import s from "./signature.module.css"
|
||||
|
||||
import { Sign } from "@/types"
|
||||
import type { Sign } from "@/types"
|
||||
import { HiOutlineShieldCheck } from "react-icons/hi"
|
||||
|
||||
import * as Tooltip from "@radix-ui/react-tooltip"
|
||||
|
||||
@@ -2,7 +2,7 @@ import s from "./filecard.module.css"
|
||||
|
||||
import Image from "next/image"
|
||||
import dynamic from "next/dynamic"
|
||||
import { FileTokenData, Sign } from "@/types"
|
||||
import type { FileTokenData, Sign } from "@/types"
|
||||
import { size, ext } from "@/lib/file"
|
||||
import { getResizedUrl } from "@/lib/image"
|
||||
import Link from "next/link"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { IMenu } from "@/types"
|
||||
import type { MenuItem } from "@/types"
|
||||
import * as NavigationMenu from "@radix-ui/react-navigation-menu"
|
||||
import classnames from "classnames/bind"
|
||||
import { useRouter } from "next/router"
|
||||
@@ -10,8 +10,8 @@ let cx = classnames.bind(s)
|
||||
|
||||
export type MenuProps = {
|
||||
vertical?: boolean
|
||||
items: IMenu[]
|
||||
onItemClick?: (item: IMenu) => void
|
||||
items: MenuItem[]
|
||||
onItemClick?: (item: MenuItem) => void
|
||||
}
|
||||
|
||||
export const Menu: React.FC<MenuProps> = ({ vertical = false, onItemClick, items }) => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import { PageMeta } from "../PageMeta"
|
||||
import { ITag } from "src/types"
|
||||
import type { Tag } from "@/types"
|
||||
|
||||
export type PageProps = {
|
||||
children?: React.ReactNode
|
||||
style?: React.CSSProperties
|
||||
date?: Date
|
||||
tags: ITag[]
|
||||
tags: Tag[]
|
||||
}
|
||||
|
||||
export const Page: React.FC<PageProps> = props => (
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import Image from "next/image"
|
||||
import { PageCardDto } from "@/types"
|
||||
import type { PageCardDto } from "@/types"
|
||||
import { CardGrid } from "../CardGrid"
|
||||
import { Card, CardLayout } from "../Card"
|
||||
import { Card } from "../Card"
|
||||
import type { CardLayout } from "../Card"
|
||||
import { Date } from "./Date"
|
||||
import { Spacer } from "../Spacer"
|
||||
import { Box } from "@/ui/Box"
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import cx from "classnames"
|
||||
import { dateFormat } from "src/lib/date"
|
||||
import { ITag } from "src/types"
|
||||
import { TagList, Direction } from "../TagList"
|
||||
import { useMobile } from "src/hooks/useMobile"
|
||||
import { dateFormat } from "@/lib/date"
|
||||
import type { Tag } from "@/types"
|
||||
import { TagList } from "../TagList"
|
||||
import type { Direction } from "../TagList"
|
||||
import { useMobile } from "@/hooks/useMobile"
|
||||
|
||||
export type PageMetaProps = {
|
||||
style?: React.CSSProperties
|
||||
date?: Date
|
||||
tags?: ITag[]
|
||||
tags?: Tag[]
|
||||
}
|
||||
|
||||
export const PageMeta: React.FC<PageMetaProps> = props => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import s from "./styles.module.css"
|
||||
|
||||
import cx from "classnames"
|
||||
import { ITag } from "src/types"
|
||||
import type { Tag } from "@/types"
|
||||
import { Button } from "@/ui/Button"
|
||||
|
||||
const directionClass = {
|
||||
@@ -11,13 +11,13 @@ const directionClass = {
|
||||
|
||||
export type Direction = "horizontal" | "vertical"
|
||||
|
||||
export type TagProps = {
|
||||
export type TagItemProps = {
|
||||
children?: React.ReactNode
|
||||
href: string
|
||||
direction: Direction
|
||||
}
|
||||
|
||||
export const Tag: React.FC<TagProps> = props => (
|
||||
export const TagItem: React.FC<TagItemProps> = props => (
|
||||
<li>
|
||||
<Button
|
||||
href={props.href}
|
||||
@@ -30,20 +30,20 @@ export const Tag: React.FC<TagProps> = props => (
|
||||
)
|
||||
|
||||
export type TagListProps = {
|
||||
items: ITag[]
|
||||
items: Tag[]
|
||||
direction: Direction
|
||||
}
|
||||
|
||||
export const TagList: React.FC<TagListProps> = props => (
|
||||
<ul className={cx(s.tags, directionClass[props.direction])}>
|
||||
{props.items.map(x => (
|
||||
<Tag
|
||||
<TagItem
|
||||
key={x.slug}
|
||||
href={x.href}
|
||||
direction={props.direction}
|
||||
>
|
||||
{x.name}
|
||||
</Tag>
|
||||
</TagItem>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
import { IMeta, Pic } from "src/types"
|
||||
import type { Meta, Pic } from "@/types"
|
||||
|
||||
export function meta(props: Partial<IMeta>): IMeta {
|
||||
export function meta(props: Partial<Meta>): Meta {
|
||||
const path = props.url || "/"
|
||||
const url = `https://art.shlisselburg.org${path}`
|
||||
|
||||
@@ -28,9 +28,9 @@ export class MetaBuilder {
|
||||
private image?: Pic
|
||||
private title?: string
|
||||
private description?: string
|
||||
private data?: Partial<IMeta>
|
||||
private data?: Partial<Meta>
|
||||
|
||||
setData(param: Partial<IMeta>) {
|
||||
setData(param: Partial<Meta>) {
|
||||
this.data = param
|
||||
return this
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
import { createHomeCards, createMenu, createPage, createPageUrls } from "@/remote/factory"
|
||||
import { IMenu, IPage, PageCardDto } from "@/types"
|
||||
import type { MenuItem, Page, PageCardDto } from "@/types"
|
||||
|
||||
const backendUrl = "https://hudozka.tmshv.com"
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function getUrls(): Promise<string[]> {
|
||||
return urls
|
||||
}
|
||||
|
||||
export async function getPageBySlug(slug: string): Promise<IPage | null> {
|
||||
export async function getPageBySlug(slug: string): Promise<Page | null> {
|
||||
const url = `${backendUrl}/pages?slug=${slug}`
|
||||
const page = await apiGet(createPage)(url, () => null)
|
||||
if (!page) {
|
||||
@@ -57,7 +57,7 @@ export async function getHomeCards(): Promise<PageCardDto[]> {
|
||||
return items
|
||||
}
|
||||
|
||||
export async function getMenu(): Promise<IMenu[]> {
|
||||
export async function getMenu(): Promise<MenuItem[]> {
|
||||
const menu = await apiGet(createMenu)(`${backendUrl}/menu`, () => [])
|
||||
return menu
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IMenu, IPage, ITag, PageCardDto, Pic, Token } from "@/types"
|
||||
import type { MenuItem, Page, Tag, PageCardDto, Pic, Token } from "@/types"
|
||||
import { asItem } from "./lib"
|
||||
import { StrapiComponentEmbed, StrapiComponent, StrapiHome, StrapiPage, StrapiPageCard, StrapiTag, StrapiMedia, StrapiMenu } from "./types"
|
||||
import { typograf, markdownToHtml } from "src/lib/text"
|
||||
import type { StrapiComponentEmbed, StrapiComponent, StrapiHome, StrapiPage, StrapiPageCard, StrapiTag, StrapiMedia, StrapiMenu } from "./types"
|
||||
import { typograf, markdownToHtml } from "@/lib/text"
|
||||
import { encodeImageToBlurhash } from "./image"
|
||||
|
||||
const md = (text: string) => typograf(markdownToHtml(text))
|
||||
@@ -95,8 +95,8 @@ export async function createPageToken(component: StrapiComponent): Promise<Token
|
||||
}
|
||||
}
|
||||
|
||||
export function createMenu(res: StrapiMenu): IMenu[] {
|
||||
const items: IMenu[] = res.menu.items.map(x => ({
|
||||
export function createMenu(res: StrapiMenu): MenuItem[] {
|
||||
const items: MenuItem[] = res.menu.items.map(x => ({
|
||||
href: x.page.slug,
|
||||
name: x.page.title,
|
||||
}))
|
||||
@@ -107,14 +107,14 @@ export function createMenu(res: StrapiMenu): IMenu[] {
|
||||
}, ...items]
|
||||
}
|
||||
|
||||
export async function createPage(res: StrapiPage | StrapiPage[]): Promise<IPage | null> {
|
||||
export async function createPage(res: StrapiPage | StrapiPage[]): Promise<Page | null> {
|
||||
const item = asItem(res)
|
||||
if (!item) {
|
||||
return null
|
||||
}
|
||||
|
||||
const cover = await getCoverImage(item.cover)
|
||||
const tags: ITag[] = item.tags.map(tag => ({
|
||||
const tags: Tag[] = item.tags.map(tag => ({
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
slug: tag.slug,
|
||||
|
||||
+8
-8
@@ -1,9 +1,9 @@
|
||||
export interface IMenu {
|
||||
export type MenuItem = {
|
||||
name: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export interface IBreadcumbsPart {
|
||||
export type BreadcrumbPart = {
|
||||
name: string
|
||||
href: string
|
||||
}
|
||||
@@ -30,7 +30,7 @@ export type FileDefinition = {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IPage {
|
||||
export type Page = {
|
||||
id: string
|
||||
title: string
|
||||
url: string
|
||||
@@ -39,8 +39,8 @@ export interface IPage {
|
||||
cover: Pic
|
||||
description: string
|
||||
tokens: Token[]
|
||||
tags: ITag[]
|
||||
breadcrumb?: IBreadcumbsPart[]
|
||||
tags: Tag[]
|
||||
breadcrumb?: BreadcrumbPart[]
|
||||
featured: boolean
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export type Sign = {
|
||||
signature: string
|
||||
}
|
||||
|
||||
export interface IDocument {
|
||||
export type DocFile = {
|
||||
// category: string
|
||||
fileName: string
|
||||
fileSize: number
|
||||
@@ -61,7 +61,7 @@ export interface IDocument {
|
||||
url: string
|
||||
}
|
||||
|
||||
export interface IMeta {
|
||||
export type Meta = {
|
||||
url: string
|
||||
siteName: string
|
||||
locale: string
|
||||
@@ -78,7 +78,7 @@ export interface IMeta {
|
||||
}
|
||||
|
||||
|
||||
export interface ITag {
|
||||
export type Tag = {
|
||||
id: number
|
||||
name: string
|
||||
slug: string
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
|
||||
Reference in new issue
Block a user