Dev #1
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* text=auto
|
||||||
137
.github/workflows/main.yaml
vendored
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
name: Build and deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Notify Telegram start building
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
message: |
|
||||||
|
🚀 Запуск сборки!
|
||||||
|
Сервер: ${{ secrets.SERVER_NAME }}
|
||||||
|
Репозиторий: ${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: smallbuster/ocenka-web:latest
|
||||||
|
build-args: |
|
||||||
|
NEXT_PUBLIC_MAIL_USER=${{ secrets.NEXT_PUBLIC_MAIL_USER }}
|
||||||
|
NEXT_PUBLIC_MAIL_PASS=${{ secrets.NEXT_PUBLIC_MAIL_PASS }}
|
||||||
|
NEXT_PUBLIC_MAIL_FROM=${{ secrets.NEXT_PUBLIC_MAIL_FROM }}
|
||||||
|
NEXT_PUBLIC_MAIL_TO=${{ secrets.NEXT_PUBLIC_MAIL_TO }}
|
||||||
|
NEXT_PUBLIC_MAIL_SECURE_KEY=${{ secrets.NEXT_PUBLIC_MAIL_SECURE_KEY }}
|
||||||
|
|
||||||
|
- name: Notify Telegram about success
|
||||||
|
if: success()
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
message: |
|
||||||
|
✅ Сборка успешно завершена!
|
||||||
|
Сервер: ${{ secrets.SERVER_NAME }}
|
||||||
|
Репозиторий: ${{ github.repository }}
|
||||||
|
Ветка: ${{ github.ref_name }}
|
||||||
|
Коммит: ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Notify Telegram about failure
|
||||||
|
if: failure()
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
message: |
|
||||||
|
❌ Сборка проекта неудачна!
|
||||||
|
Сервер: ${{ secrets.SERVER_NAME }}
|
||||||
|
Репозиторий: ${{ github.repository }}
|
||||||
|
Ветка: ${{ github.ref_name }}
|
||||||
|
Коммит: ${{ github.sha }}
|
||||||
|
Подробности: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
runs-on: self-hosted
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Notify Telegram start deploy
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
message: |
|
||||||
|
🐳 Запуск деплоя!
|
||||||
|
Сервер: ${{ secrets.SERVER_NAME }}
|
||||||
|
Репозиторий: ${{ github.repository }}
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Set up Docker Compose
|
||||||
|
run: |
|
||||||
|
docker compose version
|
||||||
|
|
||||||
|
- name: Stop and remove existing containers
|
||||||
|
run: |
|
||||||
|
docker compose -f docker-compose.yaml down || true
|
||||||
|
|
||||||
|
- name: Pull Docker images
|
||||||
|
run: |
|
||||||
|
docker compose -f docker-compose.yaml pull
|
||||||
|
|
||||||
|
- name: Start containers
|
||||||
|
run: |
|
||||||
|
docker compose -f docker-compose.yaml up -d
|
||||||
|
|
||||||
|
- name: Notify Telegram about success
|
||||||
|
if: success()
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
message: |
|
||||||
|
✅ Деплой успешно завершен!
|
||||||
|
Сервер: ${{ secrets.SERVER_NAME }}
|
||||||
|
Репозиторий: ${{ github.repository }}
|
||||||
|
Ветка: ${{ github.ref_name }}
|
||||||
|
Коммит: ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Notify Telegram about failure
|
||||||
|
if: failure()
|
||||||
|
uses: appleboy/telegram-action@master
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
message: |
|
||||||
|
❌ Деплой не удался!
|
||||||
|
Сервер: ${{ secrets.SERVER_NAME }}
|
||||||
|
Репозиторий: ${{ github.repository }}
|
||||||
|
Ветка: ${{ github.ref_name }}
|
||||||
|
Коммит: ${{ github.sha }}
|
||||||
|
Подробности: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
5
.husky/pre-commit
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
npx lint-staged
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🚀 Running test build..."
|
||||||
|
npm run build
|
||||||
6
.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="TypeScriptCompiler">
|
||||||
|
<option name="useServicePoweredTypesWasEnabledByExperiment" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/ocenka-web.iml" filepath="$PROJECT_DIR$/.idea/ocenka-web.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
12
.idea/ocenka-web.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/prettier.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PrettierConfiguration">
|
||||||
|
<option name="myConfigurationMode" value="AUTOMATIC" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
128
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AutoImportSettings">
|
||||||
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="2a96f817-9dc2-4f3c-893a-c4974c750774" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="DarkyenusTimeTracker">
|
||||||
|
<option name="totalTimeSeconds" value="226258" />
|
||||||
|
<option name="gitIntegration" value="true" />
|
||||||
|
<option name="naggedAbout" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="SCSS File" />
|
||||||
|
<option value="TypeScript JSX File" />
|
||||||
|
<option value="TypeScript File" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProblemsViewState">
|
||||||
|
<option name="selectedTabId" value="DEPENDENCY_CHECKER_PROBLEMS_TAB" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo">{
|
||||||
|
"customColor": "",
|
||||||
|
"associatedIndex": 4
|
||||||
|
}</component>
|
||||||
|
<component name="ProjectId" id="35jvwBqGA9zlZwQ6LZmQZbbmwbc" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||||
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
|
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||||
|
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
||||||
|
"git-widget-placeholder": "dev",
|
||||||
|
"ignore.virus.scanning.warn.message": "true",
|
||||||
|
"js.debugger.nextJs.config.created.client": "true",
|
||||||
|
"js.debugger.nextJs.config.created.server": "true",
|
||||||
|
"junie.onboarding.icon.badge.shown": "true",
|
||||||
|
"list.type.of.created.stylesheet": "SCSS",
|
||||||
|
"node.js.detected.package.eslint": "true",
|
||||||
|
"node.js.detected.package.tslint": "true",
|
||||||
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"npm.Next.js: server-side.executor": "Run",
|
||||||
|
"prettierjs.PrettierConfiguration.Package": "C:\\dev-personal\\ocenka-web\\node_modules\\prettier",
|
||||||
|
"settings.editor.selected.configurable": "terminal",
|
||||||
|
"to.speed.mode.migration.done": "true",
|
||||||
|
"ts.external.directory.path": "C:\\dev-personal\\ocenka-web\\node_modules\\typescript\\lib",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="C:\dev-personal\ocenka-web\public\images" />
|
||||||
|
<recent name="C:\dev-personal\ocenka-web\src\widgets" />
|
||||||
|
<recent name="C:\dev-personal\ocenka-web\src\widgets\sidebar" />
|
||||||
|
<recent name="C:\dev-personal\ocenka-web\src\views\home" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="npm.Next.js: server-side">
|
||||||
|
<configuration name="Next.js: debug client-side" type="JavascriptDebugType" uri="http://localhost:3000/">
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="Next.js: server-side" type="js.build_tools.npm">
|
||||||
|
<package-json value="$PROJECT_DIR$/package.json" />
|
||||||
|
<command value="run" />
|
||||||
|
<scripts>
|
||||||
|
<script value="dev" />
|
||||||
|
</scripts>
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SharedIndexes">
|
||||||
|
<attachedChunks>
|
||||||
|
<set>
|
||||||
|
<option value="bundled-js-predefined-d6986cc7102b-c7e53b3be11b-JavaScript-WS-253.28294.332" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="2a96f817-9dc2-4f3c-893a-c4974c750774" name="Changes" comment="" />
|
||||||
|
<created>1763636886190</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1763636886190</updated>
|
||||||
|
<workItem from="1763636887488" duration="6811000" />
|
||||||
|
<workItem from="1764586916122" duration="32000" />
|
||||||
|
<workItem from="1764590282382" duration="1201000" />
|
||||||
|
<workItem from="1764591867512" duration="4332000" />
|
||||||
|
<workItem from="1764657017067" duration="21490000" />
|
||||||
|
<workItem from="1764741053553" duration="18011000" />
|
||||||
|
<workItem from="1764825390464" duration="26961000" />
|
||||||
|
<workItem from="1765196288370" duration="332000" />
|
||||||
|
<workItem from="1765196704782" duration="4455000" />
|
||||||
|
<workItem from="1765279298291" duration="3813000" />
|
||||||
|
<workItem from="1765435962765" duration="4004000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.git
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.next
|
||||||
|
/node_modules
|
||||||
|
*.md
|
||||||
14
.prettierrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"printWidth": 80,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"singleQuote": true,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"proseWrap": "always",
|
||||||
|
"useTabs": false,
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"jsxSingleQuote": true
|
||||||
|
}
|
||||||
67
Dockerfile
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Стадия сборки (builder)
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
# Устанавливаем рабочую директорию
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Копируем package.json и package-lock.json (или yarn.lock)
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Устанавливаем зависимости
|
||||||
|
RUN npm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Копируем все файлы проекта
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Аргумент сборки для переменной окружения
|
||||||
|
ARG NEXT_PUBLIC_MAIL_USER
|
||||||
|
ARG NEXT_PUBLIC_MAIL_PASS
|
||||||
|
ARG NEXT_PUBLIC_MAIL_FROM
|
||||||
|
ARG NEXT_PUBLIC_MAIL_TO
|
||||||
|
ARG NEXT_PUBLIC_MAIL_SECURE_KEY
|
||||||
|
|
||||||
|
ENV NEXT_PUBLIC_MAIL_USER=$NEXT_PUBLIC_MAIL_USER \
|
||||||
|
NEXT_PUBLIC_MAIL_PASS=$NEXT_PUBLIC_MAIL_PASS \
|
||||||
|
NEXT_PUBLIC_MAIL_FROM=$NEXT_PUBLIC_MAIL_FROM \
|
||||||
|
NEXT_PUBLIC_MAIL_TO=$NEXT_PUBLIC_MAIL_TO \
|
||||||
|
NEXT_PUBLIC_MAIL_SECURE_KEY=$NEXT_PUBLIC_MAIL_SECURE_KEY
|
||||||
|
|
||||||
|
# Собираем приложение
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Стадия запуска (runner)
|
||||||
|
FROM node:22-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV=production \
|
||||||
|
NEXT_TELEMETRY_DISABLED=1 \
|
||||||
|
NEXT_PUBLIC_MAIL_USER=$NEXT_PUBLIC_MAIL_USER \
|
||||||
|
NEXT_PUBLIC_MAIL_PASS=$NEXT_PUBLIC_MAIL_PASS \
|
||||||
|
NEXT_PUBLIC_MAIL_FROM=$NEXT_PUBLIC_MAIL_FROM \
|
||||||
|
NEXT_PUBLIC_MAIL_TO=$NEXT_PUBLIC_MAIL_TO \
|
||||||
|
NEXT_PUBLIC_MAIL_SECURE_KEY=$NEXT_PUBLIC_MAIL_SECURE_KEY
|
||||||
|
|
||||||
|
# Включаем node пользователя
|
||||||
|
RUN addgroup -g 1001 -S nodejs
|
||||||
|
RUN adduser -S nextjs -u 1001
|
||||||
|
|
||||||
|
# Set the correct permission for prerender cache
|
||||||
|
RUN mkdir .next
|
||||||
|
RUN chown nextjs:nodejs .next
|
||||||
|
|
||||||
|
# Копируем необходимые файлы из стадии builder
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/public /app/public
|
||||||
|
|
||||||
|
# Переключаем на пользователя nextjs
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
# Открываем порт
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Запускаем приложение
|
||||||
|
ENV PORT=3000
|
||||||
|
ENV HOSTNAME="0.0.0.0"
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
35
README.md
@@ -1,36 +1 @@
|
|||||||
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.
|
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.
|
|
||||||
|
|||||||
9
docker-compose.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
ocenka-web:
|
||||||
|
image: smallbuster/ocenka-web:latest
|
||||||
|
container_name: ocenka-web
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '5003:3000'
|
||||||
@@ -11,6 +11,11 @@ const compat = new FlatCompat({
|
|||||||
|
|
||||||
const eslintConfig = [
|
const eslintConfig = [
|
||||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default eslintConfig;
|
export default eslintConfig;
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from 'next';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
/* config options here */
|
||||||
|
output: 'standalone',
|
||||||
|
sassOptions: {
|
||||||
|
includePaths: [path.resolve('./src/core/styles')],
|
||||||
|
prependData: `@import "index.scss";`,
|
||||||
|
},
|
||||||
|
compiler: {
|
||||||
|
removeConsole:
|
||||||
|
process.env.NODE_ENV === 'production' ? { exclude: ['error'] } : false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
2340
package-lock.json
generated
50
package.json
@@ -6,20 +6,50 @@
|
|||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"prettier": "prettier --write .",
|
||||||
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^19.0.0",
|
"@hookform/resolvers": "^5.1.1",
|
||||||
"react-dom": "^19.0.0",
|
"@maskito/core": "^3.9.1",
|
||||||
"next": "15.3.4"
|
"@maskito/phone": "^3.9.1",
|
||||||
|
"@maskito/react": "^3.9.1",
|
||||||
|
"libphonenumber-js": "^1.12.9",
|
||||||
|
"next": "^15.5.7",
|
||||||
|
"nodemailer": "7.0.11",
|
||||||
|
"puppeteer": "^24.32.1",
|
||||||
|
"react": "19.1.1",
|
||||||
|
"react-dom": "19.1.1",
|
||||||
|
"react-hook-form": "^7.60.0",
|
||||||
|
"react-hot-toast": "^2.5.2",
|
||||||
|
"react-yandex-metrika": "^2.6.0",
|
||||||
|
"swiper": "^11.2.10",
|
||||||
|
"zod": "^3.25.75"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"@eslint/eslintrc": "^3",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/react-dom": "^19",
|
"@types/react": "19.1.11",
|
||||||
"eslint": "^9",
|
"@types/react-dom": "19.1.8",
|
||||||
"eslint-config-next": "15.3.4",
|
"clsx": "^2.1.1",
|
||||||
"@eslint/eslintrc": "^3"
|
"eslint": "^9.39.1",
|
||||||
|
"eslint-config-next": "15.5.2",
|
||||||
|
"husky": "^9.1.7",
|
||||||
|
"lint-staged": "^16.1.2",
|
||||||
|
"prettier": "3.6.0",
|
||||||
|
"sass": "1.77.8",
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,jsx,ts,tsx}": [
|
||||||
|
"prettier --write",
|
||||||
|
"eslint --fix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@types/react": "19.1.11",
|
||||||
|
"@types/react-dom": "19.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 391 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB |
BIN
public/images/arrow.png
Normal file
|
After Width: | Height: | Size: 756 B |
BIN
public/images/bg-lines.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/images/bg-main.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
public/images/domclick_logo.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/images/dtr-logo-eagle.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/images/em-rounded-logo.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/images/licence/dtr-eac-0.png
Normal file
|
After Width: | Height: | Size: 299 KiB |
BIN
public/images/licence/dtr-eac-1.png
Normal file
|
After Width: | Height: | Size: 311 KiB |
BIN
public/images/licence/dtr-eac-2.png
Normal file
|
After Width: | Height: | Size: 313 KiB |
BIN
public/images/licence/dtr-eac-3.png
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
public/images/licence/dtr-strahovka.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
public/images/logo-dtr-white.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
public/images/ogBg.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
public/images/only-eagle-logo.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/images/partners/edinyi-centr-zashhity.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
public/images/partners/etd.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/images/partners/jur-firma-zevs.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/images/partners/laura-sochi.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/images/partners/megafon.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/images/partners/mig.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/images/partners/mts.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/images/partners/sah.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/partners/sochi-park.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
public/images/partners/sputnik.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/images/partners/transdekra.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/images/partners/yugoriya.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/photo/borodin-vitaliy.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/images/photo/elchishchev.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/images/photo/empty-photo.jpg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/photo/kaminskiy-dmitriy.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/images/photo/kolodiy-aleksandr.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/images/photo/mikova-inna.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/images/photo/mityaev-alexey.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
public/images/photo/polinov-andrey.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
public/images/photo/tlif-olga.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
public/images/photo/yancen-yana.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/sber-domclick.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
public/images/sberbank_logo.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/images/step1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/step2.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/images/step3.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/images/step4.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/images/tg-rounded-logo.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
public/images/wa-rounded-logo.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
6
public/robots.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /api/
|
||||||
|
Disallow: /cookie/
|
||||||
|
Disallow: /privacy-policy/
|
||||||
|
Disallow: /user-agreement/
|
||||||
|
Sitemap: https://ocenka-sochi.ru/sitemap.xml
|
||||||
19
public/site.webmanifest
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "ocenka-sochi.ru",
|
||||||
|
"short_name": "ocenka-sochi.ru",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "transparent",
|
||||||
|
"background_color": "transparent",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
9
public/sitemap.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>https://ocenka-sochi.ru/</loc>
|
||||||
|
<lastmod>2025-12-01</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>1.0</priority>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
1
public/svg/rotated-lines.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none' overflow='visible' height='100%' viewBox='0 0 20 16' fill='none' stroke='black' stroke-width='1' stroke-linecap='square' stroke-miterlimit='10'><g transform='translate(-12.000000, 0)'><path d='M28,0L10,18'/><path d='M18,0L0,18'/><path d='M48,0L30,18'/><path d='M38,0L20,18'/></g></svg>
|
||||||
|
After Width: | Height: | Size: 352 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 385 B |
9
src/app/api/heartbeat/route.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export async function GET(request: Request) {
|
||||||
|
return new Response('Heartbeat is OK!', {
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
56
src/app/api/og/route.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import puppeteer from 'puppeteer';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const title = req.nextUrl.searchParams.get('title') ?? '';
|
||||||
|
const desc = req.nextUrl.searchParams.get('description') ?? '';
|
||||||
|
|
||||||
|
const imageUrl = `${req.nextUrl.origin}/images/ogBg.png`;
|
||||||
|
const logoUrl = `${req.nextUrl.origin}/images/logo-dtr-white.png`;
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<html lang="ru-RU">
|
||||||
|
<body style="
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
width: 600px;
|
||||||
|
height: 315px;
|
||||||
|
background: #111;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
position: relative;
|
||||||
|
">
|
||||||
|
<img src="${imageUrl}" style="object-fit: cover; width:100%; height:100%; opacity:0.5; position:absolute; left:0; top:0;" />
|
||||||
|
<img src="${logoUrl}" style="width: 288px; height: 89px; position:absolute; left:10px; top:20px;" />
|
||||||
|
<p style="position: absolute; top: 24px; right:20px; color: white; font-family: Arial, Helvetica, sans-serif; font-size: 20px; ">
|
||||||
|
☎ +7 (900) 241-34-34
|
||||||
|
</p>
|
||||||
|
<div style="position: absolute; top: 140px; left: 20px; width:100%; height:100%;">
|
||||||
|
<h2 style="color: white; font-family: Arial, Helvetica, sans-serif; font-size: 40px; line-height: 1;">${title}</h2>
|
||||||
|
<p style="color: white; font-family: Arial, Helvetica, sans-serif; font-size: 24px; line-height: 1;">${desc}</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Запуск браузера
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await page.setContent(html, { waitUntil: 'networkidle0' });
|
||||||
|
await page.setViewport({ width: 600, height: 315 });
|
||||||
|
const screenshot = await page.screenshot({ type: 'png' });
|
||||||
|
await browser.close();
|
||||||
|
|
||||||
|
return new NextResponse(screenshot, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'image/png',
|
||||||
|
'Cache-Control': 'max-age=60',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
66
src/app/api/sendform/route.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import { TBaseForm } from '@shared/api/api.types';
|
||||||
|
import { CORE } from '@shared/config/core';
|
||||||
|
|
||||||
|
async function sendMail(data: TBaseForm) {
|
||||||
|
const { name, phone, message, form } = data;
|
||||||
|
|
||||||
|
const formattedBody = `
|
||||||
|
<html lang="ru-RU">
|
||||||
|
<body>
|
||||||
|
<p>Сообщение с сайта "Оценка-Сочи"</p>
|
||||||
|
<p>Форма отправки: ${form}</p>
|
||||||
|
<p>Имя отправителя: ${name ?? 'не указано'}</p>
|
||||||
|
<p>Номер телефона: ${phone}</p>
|
||||||
|
<p>Сообщение: ${message ?? 'отсутствует'}</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
service: 'Yandex',
|
||||||
|
auth: {
|
||||||
|
user: CORE.MAIL_USER,
|
||||||
|
pass: CORE.MAIL_PASS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await transporter.sendMail({
|
||||||
|
from: CORE.MAIL_FROM,
|
||||||
|
to: CORE.MAIL_TO,
|
||||||
|
subject: 'Заявка с сайта Ocenka-Sochi',
|
||||||
|
html: formattedBody,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
try {
|
||||||
|
const payload = await request.json();
|
||||||
|
|
||||||
|
if (payload.secure !== CORE.MAIL_SECURE_KEY) {
|
||||||
|
await Promise.reject('Request failure!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendResult = await sendMail({ ...payload });
|
||||||
|
|
||||||
|
const data = { message: 'Form accepted' };
|
||||||
|
const headers = new Headers({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
});
|
||||||
|
const options = {
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
headers: headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sendResult?.messageId) {
|
||||||
|
return new Response(JSON.stringify(data), options);
|
||||||
|
} else {
|
||||||
|
await Promise.reject('Sending request failure!');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return new Response(`Api error: ${error}`, {
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/app/contacts/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Contacts } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Контакты',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Contacts />;
|
||||||
|
}
|
||||||
20
src/app/cookies/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Cookie } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Независимая оценка и судебная экспертиза',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Cookie />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/avtotehnicheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { AutoTech } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Автотехническая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <AutoTech />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/buhgalterskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Buhgalter } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Бухгалтерская экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Buhgalter />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/dokumentov/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Document } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Экспертиза документов | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Document />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/finansovo-jekonomicheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Finans } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Финансово-экономическая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Finans />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/kadastrovaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Kadastr } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Кадастровая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Kadastr />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/kompjuterno-tehnicheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Computer } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Компьютерно-техническая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Computer />;
|
||||||
|
}
|
||||||
5
src/app/ekspertiza/ocenochnaja/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// TODO Проверить что это за страница
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <div>Page</div>;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ExpertiseCategory } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Проведение экспертиз | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ExpertizaPage() {
|
||||||
|
return <ExpertiseCategory />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/pocherkovedcheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Pocherk } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Почерковедческая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Pocherk />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/pozharno-tehnicheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Pozhar } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Пожарно-техническая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Pozhar />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/recenzirovanie/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Recenzii } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Строительно-техническая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Recenzii />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/stroitelno-tehnicheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Stroit } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Рецензирование экспертизы | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Stroit />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/tehniko-kriminalisticheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { TechCrim } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Технико-криминалистическая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <TechCrim />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/tovarovedcheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Tovar } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Товароведческая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Tovar />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/trasologicheskaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Trasologia } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Трасологическая экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Trasologia />;
|
||||||
|
}
|
||||||
12
src/app/ekspertiza/zemle-ustroitelnaja/page.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ZemStroy } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Земле-устроительная экспертиза | Компания ДИТРАСО',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре. Тел. +7 (900) 241-34-34',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <ZemStroy />;
|
||||||
|
}
|
||||||
20
src/app/experts/borodin-vitalij-petrovich/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ExpertBorodin } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Эксперт - Бородин Виталий Петрович',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <ExpertBorodin />;
|
||||||
|
}
|
||||||
20
src/app/experts/gjulmamedov-javar-firmamed-ogly/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ExpertGulmamedov } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Эксперт - Гюльмамедов Явар Фирмамед-оглы',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <ExpertGulmamedov />;
|
||||||
|
}
|
||||||
20
src/app/experts/jancen-jana-nikolaevna/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ExpertYancen } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Эксперт - Янцен Яна Николаевна',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <ExpertYancen />;
|
||||||
|
}
|
||||||
20
src/app/experts/kaminskij-dmitrij-olegovich/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ExpertKaminskiy } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Эксперт - Каминский Дмитрий Олегович',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <ExpertKaminskiy />;
|
||||||
|
}
|
||||||
20
src/app/experts/kolodij-aleksandr-sergeevich/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ExpertKolodiy } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Эксперт - Колодий Александр Сергеевич',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <ExpertKolodiy />;
|
||||||
|
}
|
||||||
20
src/app/experts/mikova-inna-georgievna/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ExpertMikova } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Эксперт - Микова Инна Георгиевна',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <ExpertMikova />;
|
||||||
|
}
|
||||||
20
src/app/experts/mitjaev-aleksej-aleksandrovich/page.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ExpertMityaev } from '@/views';
|
||||||
|
import type { Metadata } from 'next';
|
||||||
|
import { metaInfo } from '@shared/lib';
|
||||||
|
|
||||||
|
const metainfo = {
|
||||||
|
title: 'Эксперт - Митяев Алексей Александрович',
|
||||||
|
description:
|
||||||
|
'Услуги независимой оценки и судебной экспертизы в Сочи и Краснодаре.',
|
||||||
|
companyName: 'Компания ДИТРАСО',
|
||||||
|
phone: '+7 (900) 241-34-34',
|
||||||
|
url: 'https://ocenka-sochi.ru',
|
||||||
|
ogImageTitle: 'Оценка и экспертиза',
|
||||||
|
ogImageDescription: 'Независимая оценка и судебная экспертиза',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const metadata: Metadata = metaInfo(metainfo);
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <ExpertMityaev />;
|
||||||
|
}
|
||||||