Browse Source
Инфраструктура: - pnpm workspaces monorepo (apps/web, apps/api, packages/) - docker-compose.yml: PostgreSQL 16 - .env.example: DATABASE_URL, API_PORT, NEXT_PUBLIC_API_URL Backend (apps/api — NestJS 11): - Инициализирован NestJS с pnpm - Prisma 7 + prisma.config.ts подключен к PostgreSQL - Схема: User (role: viewer/editor), ExperimentalComponent (status: draft/review/approved) Frontend (apps/web — Next.js 16): - App Router, TypeScript, Tailwind CSS 4, Fira Sans (Google Fonts) - globals.css: CSS-токены бренда (цвета 053M–080M, шрифты) - layout.tsx: корневой layout с боковой навигацией - Sidebar.tsx: навигация по всем разделам (Фундамент, Компоненты, Блоки, Страницы, Оффлайн, Эксперименты) - page.tsx: редирект → /foundation/logo - /foundation/logo: полная страница «Логотип» - Иерархия и версии (Основной / Общий) - Цветовые варианты (основной, инвертированный, на форме) - Охранная зона с визуализацией - Таблица минимальных размеров (форма сотрудников) - Недопустимые варианты (6 правил) - Блок скачивания (placeholder до получения вектора) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>sprint/1
44 changed files with 14499 additions and 0 deletions
@ -0,0 +1,8 @@
|
||||
# База данных |
||||
DATABASE_URL="postgresql://brandbook:brandbook@localhost:5432/brandbook" |
||||
|
||||
# API (NestJS) |
||||
API_PORT=3001 |
||||
|
||||
# Web (Next.js) |
||||
NEXT_PUBLIC_API_URL=http://localhost:3001 |
||||
@ -0,0 +1,5 @@
|
||||
node_modules |
||||
# Keep environment variables out of version control |
||||
.env |
||||
|
||||
/generated/prisma |
||||
@ -0,0 +1,4 @@
|
||||
{ |
||||
"singleQuote": true, |
||||
"trailingComma": "all" |
||||
} |
||||
@ -0,0 +1,98 @@
|
||||
<p align="center"> |
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a> |
||||
</p> |
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 |
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest |
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p> |
||||
<p align="center"> |
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a> |
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a> |
||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a> |
||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a> |
||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a> |
||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a> |
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a> |
||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a> |
||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a> |
||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a> |
||||
</p> |
||||
<!--[](https://opencollective.com/nest#backer) |
||||
[](https://opencollective.com/nest#sponsor)--> |
||||
|
||||
## Description |
||||
|
||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. |
||||
|
||||
## Project setup |
||||
|
||||
```bash |
||||
$ pnpm install |
||||
``` |
||||
|
||||
## Compile and run the project |
||||
|
||||
```bash |
||||
# development |
||||
$ pnpm run start |
||||
|
||||
# watch mode |
||||
$ pnpm run start:dev |
||||
|
||||
# production mode |
||||
$ pnpm run start:prod |
||||
``` |
||||
|
||||
## Run tests |
||||
|
||||
```bash |
||||
# unit tests |
||||
$ pnpm run test |
||||
|
||||
# e2e tests |
||||
$ pnpm run test:e2e |
||||
|
||||
# test coverage |
||||
$ pnpm run test:cov |
||||
``` |
||||
|
||||
## Deployment |
||||
|
||||
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. |
||||
|
||||
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: |
||||
|
||||
```bash |
||||
$ pnpm install -g @nestjs/mau |
||||
$ mau deploy |
||||
``` |
||||
|
||||
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. |
||||
|
||||
## Resources |
||||
|
||||
Check out a few resources that may come in handy when working with NestJS: |
||||
|
||||
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. |
||||
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). |
||||
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). |
||||
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. |
||||
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). |
||||
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). |
||||
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). |
||||
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). |
||||
|
||||
## Support |
||||
|
||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). |
||||
|
||||
## Stay in touch |
||||
|
||||
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) |
||||
- Website - [https://nestjs.com](https://nestjs.com/) |
||||
- Twitter - [@nestframework](https://twitter.com/nestframework) |
||||
|
||||
## License |
||||
|
||||
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). |
||||
@ -0,0 +1,35 @@
|
||||
// @ts-check
|
||||
import eslint from '@eslint/js'; |
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; |
||||
import globals from 'globals'; |
||||
import tseslint from 'typescript-eslint'; |
||||
|
||||
export default tseslint.config( |
||||
{ |
||||
ignores: ['eslint.config.mjs'], |
||||
}, |
||||
eslint.configs.recommended, |
||||
...tseslint.configs.recommendedTypeChecked, |
||||
eslintPluginPrettierRecommended, |
||||
{ |
||||
languageOptions: { |
||||
globals: { |
||||
...globals.node, |
||||
...globals.jest, |
||||
}, |
||||
sourceType: 'commonjs', |
||||
parserOptions: { |
||||
projectService: true, |
||||
tsconfigRootDir: import.meta.dirname, |
||||
}, |
||||
}, |
||||
}, |
||||
{ |
||||
rules: { |
||||
'@typescript-eslint/no-explicit-any': 'off', |
||||
'@typescript-eslint/no-floating-promises': 'warn', |
||||
'@typescript-eslint/no-unsafe-argument': 'warn', |
||||
"prettier/prettier": ["error", { endOfLine: "auto" }], |
||||
}, |
||||
}, |
||||
); |
||||
@ -0,0 +1,8 @@
|
||||
{ |
||||
"$schema": "https://json.schemastore.org/nest-cli", |
||||
"collection": "@nestjs/schematics", |
||||
"sourceRoot": "src", |
||||
"compilerOptions": { |
||||
"deleteOutDir": true |
||||
} |
||||
} |
||||
@ -0,0 +1,75 @@
|
||||
{ |
||||
"name": "api", |
||||
"version": "0.0.1", |
||||
"description": "", |
||||
"author": "", |
||||
"private": true, |
||||
"license": "UNLICENSED", |
||||
"scripts": { |
||||
"build": "nest build", |
||||
"dev": "nest start --watch", |
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", |
||||
"start": "nest start", |
||||
"start:dev": "nest start --watch", |
||||
"start:debug": "nest start --debug --watch", |
||||
"start:prod": "node dist/main", |
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", |
||||
"test": "jest", |
||||
"test:watch": "jest --watch", |
||||
"test:cov": "jest --coverage", |
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", |
||||
"test:e2e": "jest --config ./test/jest-e2e.json" |
||||
}, |
||||
"dependencies": { |
||||
"@nestjs/common": "^11.0.1", |
||||
"@nestjs/core": "^11.0.1", |
||||
"@nestjs/platform-express": "^11.0.1", |
||||
"@prisma/client": "^7.5.0", |
||||
"dotenv": "^17.3.1", |
||||
"reflect-metadata": "^0.2.2", |
||||
"rxjs": "^7.8.1" |
||||
}, |
||||
"devDependencies": { |
||||
"@eslint/eslintrc": "^3.2.0", |
||||
"@eslint/js": "^9.18.0", |
||||
"@nestjs/cli": "^11.0.0", |
||||
"@nestjs/schematics": "^11.0.0", |
||||
"@nestjs/testing": "^11.0.1", |
||||
"@types/express": "^5.0.0", |
||||
"@types/jest": "^30.0.0", |
||||
"@types/node": "^22.10.7", |
||||
"@types/supertest": "^6.0.2", |
||||
"eslint": "^9.18.0", |
||||
"eslint-config-prettier": "^10.0.1", |
||||
"eslint-plugin-prettier": "^5.2.2", |
||||
"globals": "^16.0.0", |
||||
"jest": "^30.0.0", |
||||
"prettier": "^3.4.2", |
||||
"prisma": "^7.5.0", |
||||
"source-map-support": "^0.5.21", |
||||
"supertest": "^7.0.0", |
||||
"ts-jest": "^29.2.5", |
||||
"ts-loader": "^9.5.2", |
||||
"ts-node": "^10.9.2", |
||||
"tsconfig-paths": "^4.2.0", |
||||
"typescript": "^5.7.3", |
||||
"typescript-eslint": "^8.20.0" |
||||
}, |
||||
"jest": { |
||||
"moduleFileExtensions": [ |
||||
"js", |
||||
"json", |
||||
"ts" |
||||
], |
||||
"rootDir": "src", |
||||
"testRegex": ".*\\.spec\\.ts$", |
||||
"transform": { |
||||
"^.+\\.(t|j)s$": "ts-jest" |
||||
}, |
||||
"collectCoverageFrom": [ |
||||
"**/*.(t|j)s" |
||||
], |
||||
"coverageDirectory": "../coverage", |
||||
"testEnvironment": "node" |
||||
} |
||||
} |
||||
@ -0,0 +1,14 @@
|
||||
// This file was generated by Prisma, and assumes you have installed the following:
|
||||
// npm install --save-dev prisma dotenv
|
||||
import "dotenv/config"; |
||||
import { defineConfig } from "prisma/config"; |
||||
|
||||
export default defineConfig({ |
||||
schema: "prisma/schema.prisma", |
||||
migrations: { |
||||
path: "prisma/migrations", |
||||
}, |
||||
datasource: { |
||||
url: process.env["DATABASE_URL"], |
||||
}, |
||||
}); |
||||
@ -0,0 +1,42 @@
|
||||
generator client { |
||||
provider = "prisma-client" |
||||
output = "../generated/prisma" |
||||
} |
||||
|
||||
datasource db { |
||||
provider = "postgresql" |
||||
} |
||||
|
||||
enum Role { |
||||
viewer |
||||
editor |
||||
} |
||||
|
||||
enum ComponentStatus { |
||||
draft |
||||
review |
||||
approved |
||||
} |
||||
|
||||
model User { |
||||
id String @id @default(uuid()) |
||||
email String @unique |
||||
name String |
||||
passwordHash String |
||||
role Role @default(viewer) |
||||
createdAt DateTime @default(now()) |
||||
|
||||
components ExperimentalComponent[] |
||||
} |
||||
|
||||
model ExperimentalComponent { |
||||
id String @id @default(uuid()) |
||||
name String |
||||
baseComponent String |
||||
attributes Json |
||||
status ComponentStatus @default(draft) |
||||
author User @relation(fields: [authorId], references: [id]) |
||||
authorId String |
||||
createdAt DateTime @default(now()) |
||||
updatedAt DateTime @updatedAt |
||||
} |
||||
@ -0,0 +1,22 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'; |
||||
import { AppController } from './app.controller'; |
||||
import { AppService } from './app.service'; |
||||
|
||||
describe('AppController', () => { |
||||
let appController: AppController; |
||||
|
||||
beforeEach(async () => { |
||||
const app: TestingModule = await Test.createTestingModule({ |
||||
controllers: [AppController], |
||||
providers: [AppService], |
||||
}).compile(); |
||||
|
||||
appController = app.get<AppController>(AppController); |
||||
}); |
||||
|
||||
describe('root', () => { |
||||
it('should return "Hello World!"', () => { |
||||
expect(appController.getHello()).toBe('Hello World!'); |
||||
}); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common'; |
||||
import { AppService } from './app.service'; |
||||
|
||||
@Controller() |
||||
export class AppController { |
||||
constructor(private readonly appService: AppService) {} |
||||
|
||||
@Get() |
||||
getHello(): string { |
||||
return this.appService.getHello(); |
||||
} |
||||
} |
||||
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common'; |
||||
import { AppController } from './app.controller'; |
||||
import { AppService } from './app.service'; |
||||
|
||||
@Module({ |
||||
imports: [], |
||||
controllers: [AppController], |
||||
providers: [AppService], |
||||
}) |
||||
export class AppModule {} |
||||
@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common'; |
||||
|
||||
@Injectable() |
||||
export class AppService { |
||||
getHello(): string { |
||||
return 'Hello World!'; |
||||
} |
||||
} |
||||
@ -0,0 +1,8 @@
|
||||
import { NestFactory } from '@nestjs/core'; |
||||
import { AppModule } from './app.module'; |
||||
|
||||
async function bootstrap() { |
||||
const app = await NestFactory.create(AppModule); |
||||
await app.listen(process.env.PORT ?? 3000); |
||||
} |
||||
bootstrap(); |
||||
@ -0,0 +1,25 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing'; |
||||
import { INestApplication } from '@nestjs/common'; |
||||
import request from 'supertest'; |
||||
import { App } from 'supertest/types'; |
||||
import { AppModule } from './../src/app.module'; |
||||
|
||||
describe('AppController (e2e)', () => { |
||||
let app: INestApplication<App>; |
||||
|
||||
beforeEach(async () => { |
||||
const moduleFixture: TestingModule = await Test.createTestingModule({ |
||||
imports: [AppModule], |
||||
}).compile(); |
||||
|
||||
app = moduleFixture.createNestApplication(); |
||||
await app.init(); |
||||
}); |
||||
|
||||
it('/ (GET)', () => { |
||||
return request(app.getHttpServer()) |
||||
.get('/') |
||||
.expect(200) |
||||
.expect('Hello World!'); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,9 @@
|
||||
{ |
||||
"moduleFileExtensions": ["js", "json", "ts"], |
||||
"rootDir": ".", |
||||
"testEnvironment": "node", |
||||
"testRegex": ".e2e-spec.ts$", |
||||
"transform": { |
||||
"^.+\\.(t|j)s$": "ts-jest" |
||||
} |
||||
} |
||||
@ -0,0 +1,4 @@
|
||||
{ |
||||
"extends": "./tsconfig.json", |
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] |
||||
} |
||||
@ -0,0 +1,25 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"module": "nodenext", |
||||
"moduleResolution": "nodenext", |
||||
"resolvePackageJsonExports": true, |
||||
"esModuleInterop": true, |
||||
"isolatedModules": true, |
||||
"declaration": true, |
||||
"removeComments": true, |
||||
"emitDecoratorMetadata": true, |
||||
"experimentalDecorators": true, |
||||
"allowSyntheticDefaultImports": true, |
||||
"target": "ES2023", |
||||
"sourceMap": true, |
||||
"outDir": "./dist", |
||||
"baseUrl": "./", |
||||
"incremental": true, |
||||
"skipLibCheck": true, |
||||
"strictNullChecks": true, |
||||
"forceConsistentCasingInFileNames": true, |
||||
"noImplicitAny": false, |
||||
"strictBindCallApply": false, |
||||
"noFallthroughCasesInSwitch": false |
||||
} |
||||
} |
||||
@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |
||||
|
||||
# dependencies |
||||
/node_modules |
||||
/.pnp |
||||
.pnp.* |
||||
.yarn/* |
||||
!.yarn/patches |
||||
!.yarn/plugins |
||||
!.yarn/releases |
||||
!.yarn/versions |
||||
|
||||
# testing |
||||
/coverage |
||||
|
||||
# next.js |
||||
/.next/ |
||||
/out/ |
||||
|
||||
# production |
||||
/build |
||||
|
||||
# misc |
||||
.DS_Store |
||||
*.pem |
||||
|
||||
# debug |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
.pnpm-debug.log* |
||||
|
||||
# env files (can opt-in for committing if needed) |
||||
.env* |
||||
|
||||
# vercel |
||||
.vercel |
||||
|
||||
# typescript |
||||
*.tsbuildinfo |
||||
next-env.d.ts |
||||
@ -0,0 +1,5 @@
|
||||
<!-- BEGIN:nextjs-agent-rules --> |
||||
# This is NOT the Next.js you know |
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. |
||||
<!-- END:nextjs-agent-rules --> |
||||
@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). |
||||
|
||||
## Getting Started |
||||
|
||||
First, run the development server: |
||||
|
||||
```bash |
||||
npm run dev |
||||
# or |
||||
yarn dev |
||||
# or |
||||
pnpm dev |
||||
# or |
||||
bun dev |
||||
``` |
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. |
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. |
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. |
||||
|
||||
## Learn More |
||||
|
||||
To learn more about Next.js, take a look at the following resources: |
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. |
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. |
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! |
||||
|
||||
## Deploy on Vercel |
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. |
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. |
||||
|
After Width: | Height: | Size: 25 KiB |
@ -0,0 +1,44 @@
|
||||
@import "tailwindcss"; |
||||
|
||||
/* ─── Бренд-токены О!Клиника ─────────────────────────────────────────── */ |
||||
/* Цвета уточняются в Sprint 2 по таблице Oracal */ |
||||
:root { |
||||
/* Фирменные цвета (приблизительно — уточнить в Sprint 2) */ |
||||
--brand-053m: #7ecfca; /* Основной бирюзовый (Oracal 053M) */ |
||||
--brand-073m: #5b7b87; /* Тёмный серо-голубой (Oracal 073M) */ |
||||
--brand-066m: #5bb5ad; /* Средний бирюзовый (Oracal 066M) */ |
||||
--brand-050m: #1b4c72; /* Тёмно-синий, наружная реклама (Oracal 050M) */ |
||||
--brand-081m: #c4a882; /* Бежевый (Oracal 081M) */ |
||||
--brand-080m: #5c2e0e; /* Тёмно-коричневый (Oracal 080M) */ |
||||
--brand-white: #ffffff; |
||||
|
||||
/* UI-цвета брендбука */ |
||||
--bb-sidebar-bg: #f8f9fa; |
||||
--bb-sidebar-border: #e5e7eb; |
||||
--bb-sidebar-text: #374151; |
||||
--bb-sidebar-text-muted: #6b7280; |
||||
--bb-sidebar-active-bg: #e0f5f4; |
||||
--bb-sidebar-active-text: var(--brand-053m); |
||||
--bb-sidebar-section: #9ca3af; |
||||
--bb-content-bg: #ffffff; |
||||
--bb-border: #e5e7eb; |
||||
--bb-text: #111827; |
||||
--bb-text-muted: #6b7280; |
||||
|
||||
/* Шрифты */ |
||||
--font-brand: 'DINPro', 'DIN Pro', Arial, sans-serif; |
||||
--font-web: 'Fira Sans', sans-serif; |
||||
--font-mono: 'JetBrains Mono', 'Courier New', monospace; |
||||
} |
||||
|
||||
@theme inline { |
||||
--color-background: #ffffff; |
||||
--color-foreground: var(--bb-text); |
||||
} |
||||
|
||||
body { |
||||
font-family: var(--font-web); |
||||
background: var(--bb-content-bg); |
||||
color: var(--bb-text); |
||||
-webkit-font-smoothing: antialiased; |
||||
} |
||||
@ -0,0 +1,33 @@
|
||||
import type { Metadata } from "next"; |
||||
import { Fira_Sans } from "next/font/google"; |
||||
import "./globals.css"; |
||||
import { Sidebar } from "@/components/layout/Sidebar"; |
||||
|
||||
const firaSans = Fira_Sans({ |
||||
subsets: ["latin", "cyrillic"], |
||||
weight: ["300", "400", "500", "600"], |
||||
variable: "--font-fira-sans", |
||||
display: "swap", |
||||
}); |
||||
|
||||
export const metadata: Metadata = { |
||||
title: "Цифровой брендбук | Клиника УХО•ГОРЛО•НОС им. проф. Е.Н. Оленевой", |
||||
description: "Интерактивный брендбук — Living Styleguide oclinica.ru", |
||||
}; |
||||
|
||||
export default function RootLayout({ |
||||
children, |
||||
}: { |
||||
children: React.ReactNode; |
||||
}) { |
||||
return ( |
||||
<html lang="ru" className={firaSans.variable}> |
||||
<body className="flex h-screen overflow-hidden bg-white"> |
||||
<Sidebar /> |
||||
<main className="flex-1 overflow-y-auto"> |
||||
{children} |
||||
</main> |
||||
</body> |
||||
</html> |
||||
); |
||||
} |
||||
@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation"; |
||||
|
||||
export default function RootPage() { |
||||
redirect("/foundation/logo"); |
||||
} |
||||
@ -0,0 +1,178 @@
|
||||
"use client"; |
||||
|
||||
import Link from "next/link"; |
||||
import { usePathname } from "next/navigation"; |
||||
|
||||
type NavItem = { |
||||
label: string; |
||||
href: string; |
||||
soon?: boolean; |
||||
}; |
||||
|
||||
type NavSection = { |
||||
title: string; |
||||
items: NavItem[]; |
||||
}; |
||||
|
||||
const NAV: NavSection[] = [ |
||||
{ |
||||
title: "Фундамент", |
||||
items: [ |
||||
{ label: "Логотип", href: "/foundation/logo" }, |
||||
{ label: "Цвета", href: "/foundation/colors", soon: true }, |
||||
{ label: "Типографика", href: "/foundation/typography", soon: true }, |
||||
{ label: "Иконография", href: "/foundation/icons", soon: true }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "Компоненты", |
||||
items: [ |
||||
{ label: "Кнопки", href: "/components/buttons", soon: true }, |
||||
{ label: "Форм-контролы", href: "/components/forms", soon: true }, |
||||
{ label: "Карточки", href: "/components/cards", soon: true }, |
||||
{ label: "Бейджи и теги", href: "/components/badges", soon: true }, |
||||
{ label: "Алерты", href: "/components/alerts", soon: true }, |
||||
{ label: "Модальные окна", href: "/components/modals", soon: true }, |
||||
{ label: "Таблицы", href: "/components/tables", soon: true }, |
||||
{ label: "Навигация", href: "/components/navigation", soon: true }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "Блоки", |
||||
items: [ |
||||
{ label: "Hero", href: "/blocks/hero", soon: true }, |
||||
{ label: "CEO-текст", href: "/blocks/ceo", soon: true }, |
||||
{ label: "Наши врачи", href: "/blocks/doctors", soon: true }, |
||||
{ label: "Отзывы", href: "/blocks/reviews", soon: true }, |
||||
{ label: "Новости", href: "/blocks/news", soon: true }, |
||||
{ label: "Формы контакта", href: "/blocks/contact-forms", soon: true }, |
||||
{ label: "Контакт", href: "/blocks/contact", soon: true }, |
||||
{ label: "Услуги", href: "/blocks/services", soon: true }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "Страницы", |
||||
items: [ |
||||
{ label: "Главная", href: "/pages/home", soon: true }, |
||||
{ label: "Заболевание", href: "/pages/disease", soon: true }, |
||||
{ label: "Все врачи", href: "/pages/doctors", soon: true }, |
||||
{ label: "Врач", href: "/pages/doctor", soon: true }, |
||||
{ label: "Цены", href: "/pages/prices", soon: true }, |
||||
{ label: "Контакты", href: "/pages/contacts", soon: true }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "Оффлайн элементы", |
||||
items: [ |
||||
{ label: "Форма сотрудников", href: "/offline/uniform", soon: true }, |
||||
{ label: "Бейджи", href: "/offline/badges", soon: true }, |
||||
{ label: "Навигация", href: "/offline/navigation", soon: true }, |
||||
{ label: "Транспорт", href: "/offline/transport", soon: true }, |
||||
{ label: "Печать", href: "/offline/print", soon: true }, |
||||
], |
||||
}, |
||||
{ |
||||
title: "Эксперименты", |
||||
items: [ |
||||
{ label: "Библиотека", href: "/experiments", soon: true }, |
||||
], |
||||
}, |
||||
]; |
||||
|
||||
export function Sidebar() { |
||||
const pathname = usePathname(); |
||||
|
||||
return ( |
||||
<aside |
||||
className="w-64 shrink-0 h-screen overflow-y-auto border-r flex flex-col" |
||||
style={{ |
||||
background: "var(--bb-sidebar-bg)", |
||||
borderColor: "var(--bb-sidebar-border)", |
||||
}} |
||||
> |
||||
{/* Логотип брендбука */} |
||||
<div |
||||
className="px-5 py-4 border-b" |
||||
style={{ borderColor: "var(--bb-sidebar-border)" }} |
||||
> |
||||
<p |
||||
className="text-xs font-semibold uppercase tracking-widest mb-0.5" |
||||
style={{ color: "var(--brand-053m)" }} |
||||
> |
||||
Брендбук |
||||
</p> |
||||
<p |
||||
className="text-xs leading-tight" |
||||
style={{ color: "var(--bb-sidebar-text-muted)" }} |
||||
> |
||||
Клиника УХО•ГОРЛО•НОС |
||||
<br /> |
||||
им. проф. Е.Н. Оленевой |
||||
</p> |
||||
</div> |
||||
|
||||
{/* Навигация */} |
||||
<nav className="flex-1 px-3 py-4 space-y-5"> |
||||
{NAV.map((section) => ( |
||||
<div key={section.title}> |
||||
<p |
||||
className="px-2 mb-1 text-[10px] font-semibold uppercase tracking-widest" |
||||
style={{ color: "var(--bb-sidebar-section)" }} |
||||
> |
||||
{section.title} |
||||
</p> |
||||
<ul className="space-y-0.5"> |
||||
{section.items.map((item) => { |
||||
const isActive = pathname === item.href; |
||||
return ( |
||||
<li key={item.href}> |
||||
<Link |
||||
href={item.soon ? "#" : item.href} |
||||
className="flex items-center justify-between px-2 py-1.5 rounded-md text-sm transition-colors" |
||||
style={{ |
||||
color: isActive |
||||
? "var(--brand-073m)" |
||||
: item.soon |
||||
? "var(--bb-sidebar-text-muted)" |
||||
: "var(--bb-sidebar-text)", |
||||
background: isActive |
||||
? "var(--bb-sidebar-active-bg)" |
||||
: "transparent", |
||||
fontWeight: isActive ? 500 : 400, |
||||
cursor: item.soon ? "default" : "pointer", |
||||
}} |
||||
> |
||||
{item.label} |
||||
{item.soon && ( |
||||
<span |
||||
className="text-[10px] px-1.5 py-0.5 rounded" |
||||
style={{ |
||||
background: "#f3f4f6", |
||||
color: "var(--bb-sidebar-section)", |
||||
}} |
||||
> |
||||
скоро |
||||
</span> |
||||
)} |
||||
</Link> |
||||
</li> |
||||
); |
||||
})} |
||||
</ul> |
||||
</div> |
||||
))} |
||||
</nav> |
||||
|
||||
{/* Версия */} |
||||
<div |
||||
className="px-5 py-3 border-t text-xs" |
||||
style={{ |
||||
borderColor: "var(--bb-sidebar-border)", |
||||
color: "var(--bb-sidebar-text-muted)", |
||||
}} |
||||
> |
||||
Sprint 1 · v0.1.0 |
||||
</div> |
||||
</aside> |
||||
); |
||||
} |
||||
@ -0,0 +1,18 @@
|
||||
import { defineConfig, globalIgnores } from "eslint/config"; |
||||
import nextVitals from "eslint-config-next/core-web-vitals"; |
||||
import nextTs from "eslint-config-next/typescript"; |
||||
|
||||
const eslintConfig = defineConfig([ |
||||
...nextVitals, |
||||
...nextTs, |
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([ |
||||
// Default ignores of eslint-config-next:
|
||||
".next/**", |
||||
"out/**", |
||||
"build/**", |
||||
"next-env.d.ts", |
||||
]), |
||||
]); |
||||
|
||||
export default eslintConfig; |
||||
@ -0,0 +1,7 @@
|
||||
import type { NextConfig } from "next"; |
||||
|
||||
const nextConfig: NextConfig = { |
||||
/* config options here */ |
||||
}; |
||||
|
||||
export default nextConfig; |
||||
@ -0,0 +1,26 @@
|
||||
{ |
||||
"name": "web", |
||||
"version": "0.1.0", |
||||
"private": true, |
||||
"scripts": { |
||||
"dev": "next dev", |
||||
"build": "next build", |
||||
"start": "next start", |
||||
"lint": "eslint" |
||||
}, |
||||
"dependencies": { |
||||
"next": "16.2.1", |
||||
"react": "19.2.4", |
||||
"react-dom": "19.2.4" |
||||
}, |
||||
"devDependencies": { |
||||
"@tailwindcss/postcss": "^4", |
||||
"@types/node": "^20", |
||||
"@types/react": "^19", |
||||
"@types/react-dom": "^19", |
||||
"eslint": "^9", |
||||
"eslint-config-next": "16.2.1", |
||||
"tailwindcss": "^4", |
||||
"typescript": "^5" |
||||
} |
||||
} |
||||
@ -0,0 +1,3 @@
|
||||
ignoredBuiltDependencies: |
||||
- sharp |
||||
- unrs-resolver |
||||
@ -0,0 +1,7 @@
|
||||
const config = { |
||||
plugins: { |
||||
"@tailwindcss/postcss": {}, |
||||
}, |
||||
}; |
||||
|
||||
export default config; |
||||
@ -0,0 +1,34 @@
|
||||
{ |
||||
"compilerOptions": { |
||||
"target": "ES2017", |
||||
"lib": ["dom", "dom.iterable", "esnext"], |
||||
"allowJs": true, |
||||
"skipLibCheck": true, |
||||
"strict": true, |
||||
"noEmit": true, |
||||
"esModuleInterop": true, |
||||
"module": "esnext", |
||||
"moduleResolution": "bundler", |
||||
"resolveJsonModule": true, |
||||
"isolatedModules": true, |
||||
"jsx": "react-jsx", |
||||
"incremental": true, |
||||
"plugins": [ |
||||
{ |
||||
"name": "next" |
||||
} |
||||
], |
||||
"paths": { |
||||
"@/*": ["./*"] |
||||
} |
||||
}, |
||||
"include": [ |
||||
"next-env.d.ts", |
||||
"**/*.ts", |
||||
"**/*.tsx", |
||||
".next/types/**/*.ts", |
||||
".next/dev/types/**/*.ts", |
||||
"**/*.mts" |
||||
], |
||||
"exclude": ["node_modules"] |
||||
} |
||||
@ -0,0 +1,16 @@
|
||||
services: |
||||
postgres: |
||||
image: postgres:16-alpine |
||||
container_name: brandbook_postgres |
||||
restart: unless-stopped |
||||
environment: |
||||
POSTGRES_USER: brandbook |
||||
POSTGRES_PASSWORD: brandbook |
||||
POSTGRES_DB: brandbook |
||||
ports: |
||||
- "5432:5432" |
||||
volumes: |
||||
- postgres_data:/var/lib/postgresql/data |
||||
|
||||
volumes: |
||||
postgres_data: |
||||
@ -0,0 +1,14 @@
|
||||
{ |
||||
"name": "oclinica-brandbook", |
||||
"version": "0.0.1", |
||||
"private": true, |
||||
"scripts": { |
||||
"dev": "pnpm --parallel -r dev", |
||||
"build": "pnpm --parallel -r build", |
||||
"lint": "pnpm --parallel -r lint" |
||||
}, |
||||
"engines": { |
||||
"node": ">=20", |
||||
"pnpm": ">=9" |
||||
} |
||||
} |
||||
Loading…
Reference in new issue