diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..1e81003
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,8 @@
+node_modules
+dist
+.dockerignore
+Dockerfile
+docker-compose.yml
+.git
+.gitlab-ci.yml
+*.log
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..1534ce2
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,40 @@
+name: Build
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '18'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build project
+ run: npm run build
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: build-files-${{ github.sha }}
+ path: dist/
+ retention-days: 7
+
+ - name: Build summary
+ run: |
+ echo "✅ Build completed successfully" >> $GITHUB_STEP_SUMMARY
+ echo "📦 Artifacts uploaded as: build-files-${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
\ No newline at end of file
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..8a5231d
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,73 @@
+name: Deploy to Server
+on:
+ push:
+ branches: [ main ]
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run lint
+ run: npm run lint
+
+ - name: TypeScript check
+ run: npx tsc --noEmit
+
+ - name: Build project
+ run: npm run build
+
+ - name: Deploy to server
+ uses: appleboy/ssh-action@v1.0.0
+ with:
+ host: ${{ secrets.SERVER_HOST }}
+ username: ${{ secrets.SERVER_USER }}
+ key: ${{ secrets.SERVER_SSH_KEY }}
+ port: ${{ secrets.SERVER_PORT || 22 }}
+ script: |
+ echo "🚀 Starting deployment..."
+
+ cd /opt
+
+ if [ ! -d "frontend-react" ]; then
+ echo "📥 Cloning repository..."
+ git clone https://github.com/PrettyPet-Organization/frontend-react.git
+ else
+ echo "🔄 Updating repository..."
+ cd frontend-react
+ git fetch origin
+ git reset --hard origin/main
+ cd ..
+ fi
+
+ cd frontend-react
+
+ echo "⏹️ Stopping old containers..."
+ docker-compose down || true
+
+ echo "🔨 Building and starting containers..."
+ docker-compose up -d --build
+
+ echo "⏳ Waiting for startup..."
+ sleep 10
+
+ if docker-compose ps | grep -q "Up"; then
+ echo "✅ Deployment successful!"
+ else
+ echo "❌ Something went wrong"
+ docker-compose logs
+ exit 1
+ fi
+
+ docker-compose ps
\ No newline at end of file
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..b4ff684
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,50 @@
+name: Lint
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+ workflow_dispatch:
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '18'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run ESLint
+ run: npm run lint
+ continue-on-error: false
+
+ - name: Run TypeScript check
+ run: npx tsc --noEmit
+
+ - name: Run Prettier check
+ run: |
+ if npm run format:check; then
+ echo "✅ Code formatting is correct"
+ else
+ echo "❌ Code formatting issues found. Run 'npm run format' to fix."
+ exit 1
+ fi
+ continue-on-error: false
+
+ - name: Lint summary
+ if: always()
+ run: |
+ echo "## Lint Results" >> $GITHUB_STEP_SUMMARY
+ echo "- ESLint: ${{ steps.eslint.outcome || '✅ Passed' }}" >> $GITHUB_STEP_SUMMARY
+ echo "- TypeScript: ${{ steps.typescript.outcome || '✅ Passed' }}" >> $GITHUB_STEP_SUMMARY
+ echo "- Prettier: ${{ steps.prettier.outcome || '✅ Passed' }}" >> $GITHUB_STEP_SUMMARY
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a547bf3..26d5547 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+
+# enviroment
+.env
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..6eeb4f8
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,16 @@
+FROM node:20-alpine3.19 AS builder
+
+WORKDIR /app
+COPY package*.json ./
+RUN npm ci
+COPY . .
+RUN npm run build
+
+FROM nginx:alpine
+
+COPY --from=builder /app/dist /usr/share/nginx/html
+
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..89254d6
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,13 @@
+version: '3.8'
+
+services:
+ frontend:
+ build: .
+ container_name: prettypet-frontend
+ restart: unless-stopped
+ ports:
+ - "80:80"
+ volumes:
+ - ./logs:/var/log/nginx
+ environment:
+ - NODE_ENV=production
\ No newline at end of file
diff --git a/index.html b/index.html
index e4b78ea..3ec9eeb 100644
--- a/index.html
+++ b/index.html
@@ -2,9 +2,9 @@
-
+
- Vite + React + TS
+ Pretty Pet
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..f6a0496
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,43 @@
+events {
+ worker_connections 1024;
+}
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ access_log /var/log/nginx/access.log;
+ error_log /var/log/nginx/error.log;
+
+ gzip on;
+ gzip_vary on;
+ gzip_min_length 1024;
+ gzip_proxied expired no-cache no-store private must-revalidate auth;
+ gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
+
+ server {
+ listen 80;
+ server_name localhost;
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ }
+
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header X-XSS-Protection "1; mode=block" always;
+
+ location /health {
+ access_log off;
+ return 200 "healthy\n";
+ add_header Content-Type text/plain;
+ }
+ }
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index ca0e103..23e9222 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"@reduxjs/toolkit": "^2.8.2",
"@tailwindcss/vite": "^4.1.10",
"antd": "^5.26.1",
+ "axios": "^1.11.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-redux": "^9.2.0",
@@ -2733,6 +2734,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
"node_modules/autoprefixer": {
"version": "10.4.21",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
@@ -2771,6 +2778,17 @@
"postcss": "^8.1.0"
}
},
+ "node_modules/axios": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
+ "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2842,6 +2860,19 @@
"devOptional": true,
"license": "MIT/X11"
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -2948,6 +2979,18 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/commander": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
@@ -3055,6 +3098,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
@@ -3077,6 +3129,20 @@
"node": ">=8"
}
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.170",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.170.tgz",
@@ -3104,6 +3170,51 @@
"node": ">=10.13.0"
}
},
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/esbuild": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
@@ -3516,6 +3627,42 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -3559,6 +3706,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -3569,6 +3725,43 @@
"node": ">=6.9.0"
}
},
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/gh-pages": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.3.0.tgz",
@@ -3639,6 +3832,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -3662,6 +3867,45 @@
"node": ">=8"
}
},
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4164,6 +4408,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -4188,6 +4441,27 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4529,6 +4803,12 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
diff --git a/package.json b/package.json
index 1336758..18414ed 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"@reduxjs/toolkit": "^2.8.2",
"@tailwindcss/vite": "^4.1.10",
"antd": "^5.26.1",
+ "axios": "^1.11.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-redux": "^9.2.0",
diff --git a/public/index.html b/public/index.html
index f4e5fd2..658e79a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,7 +2,7 @@
-
+
Pretty Pet
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 0000000..3c5994f
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1,947 @@
+
\ No newline at end of file
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/app/.gitkeep b/src/app/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/app/AppRouter.tsx b/src/app/AppRouter.tsx
index edd8407..d764de5 100644
--- a/src/app/AppRouter.tsx
+++ b/src/app/AppRouter.tsx
@@ -1,25 +1,32 @@
import { Routes, Route } from 'react-router-dom';
-import {WelcomePage} from "../pages/Welcome";
-import {LoginPage} from "../pages/Login";
-import {RegisterPage} from "../pages/Register";
import ProtectedRoute from "../shared/ui/ProtectedRoute.tsx";
-import { AuthorizedPage } from '../pages/Authorized/index.ts';
+import {WelcomePage} from "../pages/Welcome/WelcomePage.tsx";
+import {LoginPage} from "../pages/Login/LoginPage.tsx";
+import {RegisterPage} from "../pages/Register/RegisterPage.tsx";
+import {AuthorizedPage} from "../pages/Authorized/AuthorizedPage.tsx";
+import AuthorizedLayout from "../widgets/AuthorizedLayout/AuthorizedLayout.tsx";
+import {AuthorizedRoutes} from "./routes/AuthorizedRoutes.ts";
export const AppRouter = () => {
- return (
-
- } />
- } />
- } />
-
-
-
- }
- />
- 404 - Страница не найдена} />
-
- );
+ return (
+
+ }/>
+ }/>
+ }/>
+ {AuthorizedRoutes.map(({path, element: Component}) => {
+ return
+
+
+
+
+ }
+ />
+ })}
+ 404 - Страница не найдена}/>
+
+ );
};
\ No newline at end of file
diff --git a/src/app/routes/AuthorizedRoutes.ts b/src/app/routes/AuthorizedRoutes.ts
new file mode 100644
index 0000000..ee302f9
--- /dev/null
+++ b/src/app/routes/AuthorizedRoutes.ts
@@ -0,0 +1,22 @@
+import type {FC} from "react";
+import {AuthorizedPage} from "../../pages/Authorized/AuthorizedPage.tsx";
+import ProjectCatalogPage from "../../pages/ProjectCatalog/ProjectCatalogPage.tsx";
+import ProfilePage from "../../pages/Profile/ProfilePage.tsx";
+
+export const AuthorizedRoutes: {
+ path: string;
+ element: FC
+}[] = [
+ {
+ path: 'authorized',
+ element: AuthorizedPage
+ },
+ {
+ path: 'catalog',
+ element: ProjectCatalogPage
+ },
+ {
+ path: 'profile',
+ element: ProfilePage
+ }
+]
\ No newline at end of file
diff --git a/src/app/store/index.ts b/src/app/store/index.ts
index 036665d..891fc7a 100644
--- a/src/app/store/index.ts
+++ b/src/app/store/index.ts
@@ -1,11 +1,13 @@
import { configureStore } from '@reduxjs/toolkit';
import userReducer from '../../entities/User/model/userSlice';
import themeReducer from '../../entities/Theme/model/themeSlice';
+import mobileMenuReducer from '../../entities/MobileMenu/mobileMenuSlice';
export const store = configureStore({
reducer: {
user: userReducer,
theme: themeReducer,
+ mobileMenu: mobileMenuReducer
},
});
diff --git a/src/entities/MobileMenu/mobileMenuSlice.ts b/src/entities/MobileMenu/mobileMenuSlice.ts
new file mode 100644
index 0000000..165fd48
--- /dev/null
+++ b/src/entities/MobileMenu/mobileMenuSlice.ts
@@ -0,0 +1,28 @@
+import {createSlice} from "@reduxjs/toolkit";
+
+interface MobileMenuState {
+ isOpened: boolean;
+}
+
+const initialState: MobileMenuState = {
+ isOpened: false,
+}
+
+export const mobileMenuSlice = createSlice({
+ name: 'mobileMenu',
+ initialState,
+ reducers: {
+ open: (state) => {
+ state.isOpened = true;
+ },
+ close: (state) => {
+ state.isOpened = false;
+ },
+ toggle: (state) => {
+ state.isOpened = !state.isOpened;
+ }
+ }
+});
+
+export const {open, close, toggle} = mobileMenuSlice.actions;
+export default mobileMenuSlice.reducer;
\ No newline at end of file
diff --git a/src/features/.gitkeep b/src/features/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/features/AuthorizedNavBar/AuthorizedNavBar.tsx b/src/features/AuthorizedNavBar/AuthorizedNavBar.tsx
new file mode 100644
index 0000000..f40611b
--- /dev/null
+++ b/src/features/AuthorizedNavBar/AuthorizedNavBar.tsx
@@ -0,0 +1,45 @@
+import {useNavigate} from "react-router-dom";
+import type {FC} from "react";
+import {useMobileMenu} from "../../shared/lib/hooks/useMobuleMenu.ts";
+
+const AuthorizedNavBar: FC = () => {
+
+ const navigate = useNavigate();
+
+ const {closeMobileMenu} = useMobileMenu()
+
+ const authorizedRoutList: {name: string, link: string}[] = [
+ {
+ name: 'Главная',
+ link: '/authorized',
+ },
+ {
+ name: 'Все проекты',
+ link: '/catalog',
+ },
+ {
+ name: 'Профиль',
+ link: '/profile',
+ }
+ ]
+
+ const handleNavigate = (link: string) => {
+ closeMobileMenu();
+ navigate(link)
+ }
+
+ return (
+
+ {authorizedRoutList.map((item, i) => (
+
{ handleNavigate(item.link) }}
+ >
+ {item.name}
+
+ ))}
+
+ );
+};
+
+export default AuthorizedNavBar;
\ No newline at end of file
diff --git a/src/features/LogoutButton/LogoutButton.tsx b/src/features/LogoutButton/LogoutButton.tsx
new file mode 100644
index 0000000..68f2db2
--- /dev/null
+++ b/src/features/LogoutButton/LogoutButton.tsx
@@ -0,0 +1,27 @@
+import {Button} from "antd";
+import {useNavigate} from "react-router-dom";
+import {useAuth} from "../../shared/lib/hooks/useAuth.ts";
+import {LogoutOutlined} from "@ant-design/icons";
+
+const LogoutButton = () => {
+
+ const navigate = useNavigate();
+ const {logoutUser} = useAuth();
+
+ const handleLogout = () => {
+ logoutUser();
+ navigate('/');
+ };
+
+ return (
+
+ );
+};
+
+export default LogoutButton;
\ No newline at end of file
diff --git a/src/features/MobileMenuToggle/MobileMenuToggle.tsx b/src/features/MobileMenuToggle/MobileMenuToggle.tsx
new file mode 100644
index 0000000..a73af5b
--- /dev/null
+++ b/src/features/MobileMenuToggle/MobileMenuToggle.tsx
@@ -0,0 +1,15 @@
+import {useMobileMenu} from "../../shared/lib/hooks/useMobuleMenu.ts";
+import {CloseOutlined, MenuOutlined} from "@ant-design/icons";
+
+const MobileMenuToggle = () => {
+
+ const {isOpened, toggleMobileMenu} = useMobileMenu();
+
+ return (
+
+ );
+};
+
+export default MobileMenuToggle;
\ No newline at end of file
diff --git a/src/features/ThemeToggle/ui/ThemeToggle.tsx b/src/features/ThemeToggle/ui/ThemeToggle.tsx
index b54b5cf..015772e 100644
--- a/src/features/ThemeToggle/ui/ThemeToggle.tsx
+++ b/src/features/ThemeToggle/ui/ThemeToggle.tsx
@@ -56,7 +56,7 @@ export const ThemeToggle: React.FC = () => {
type="text"
icon={currentTheme === 'dark' ? : }
onClick={toggleCurrentTheme}
- className="theme-toggle-btn hover:bg-theme-hover transition-colors"
+ className="btn hover:bg-theme-hover transition-colors"
size="large"
/>
diff --git a/src/pages/.gitkeep b/src/pages/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/pages/Authorized/AuthorizedPage.tsx b/src/pages/Authorized/AuthorizedPage.tsx
new file mode 100644
index 0000000..b535b70
--- /dev/null
+++ b/src/pages/Authorized/AuthorizedPage.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+import {useAuth} from "../../shared/lib/hooks/useAuth.ts";
+
+export const AuthorizedPage: React.FC = () => {
+ const {user} = useAuth();
+
+
+ return (
+
+
+
+ Авторизованная страница
+
+
+
+ Вы успешно вошли в систему! Эта страница доступна только авторизованным пользователям.
+
+
+
+
+ Информация о пользователе
+
+
+ Имя: {user?.name}
+
+
+ Email: {user?.email}
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/pages/Authorized/index.ts b/src/pages/Authorized/index.ts
deleted file mode 100644
index 10458f5..0000000
--- a/src/pages/Authorized/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { AuthorizedPage } from './ui/AuthorizedPage';
\ No newline at end of file
diff --git a/src/pages/Authorized/ui/AuthorizedPage.tsx b/src/pages/Authorized/ui/AuthorizedPage.tsx
deleted file mode 100644
index 5a67919..0000000
--- a/src/pages/Authorized/ui/AuthorizedPage.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react';
-import { useNavigate } from 'react-router-dom';
-import { useAuth } from '../../../shared/lib/hooks/useAuth';
-import { ThemeToggle } from '../../../features/ThemeToggle/ui/ThemeToggle';
-import { ASSETS } from '../../../shared/config/assets';
-
-export const AuthorizedPage: React.FC = () => {
- const navigate = useNavigate();
- const { user, logoutUser } = useAuth();
-
- const handleLogout = () => {
- logoutUser();
- navigate('/');
- };
-
- return (
-
- {/* Header */}
-
-
- {/* Основной контент */}
-
-
- Авторизованная страница
-
-
-
- Вы успешно вошли в систему! Эта страница доступна только авторизованным пользователям.
-
-
-
-
- Информация о пользователе
-
-
- Имя: {user?.name}
-
-
- Email: {user?.email}
-
-
-
-
- {/* Footer */}
-
-
- );
-};
\ No newline at end of file
diff --git a/src/pages/Login/ui/LoginPage.tsx b/src/pages/Login/LoginPage.tsx
similarity index 77%
rename from src/pages/Login/ui/LoginPage.tsx
rename to src/pages/Login/LoginPage.tsx
index dbad738..a3e91fd 100644
--- a/src/pages/Login/ui/LoginPage.tsx
+++ b/src/pages/Login/LoginPage.tsx
@@ -1,7 +1,8 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
-import { useAuth } from '../../../shared/lib/hooks/useAuth';
-import UnauthorizedLayout from '../../../widgets/UnauthorizedLayout/UnauthorizedLayout';
+import {Button} from "antd";
+import UnauthorizedLayout from "../../widgets/UnauthorizedLayout/UnauthorizedLayout.tsx";
+import {useAuth} from "../../shared/lib/hooks/useAuth.ts";
export const LoginPage: React.FC = () => {
const navigate = useNavigate();
@@ -32,22 +33,24 @@ export const LoginPage: React.FC = () => {
Для демонстрации просто нажмите кнопку входа
-
+
Нет аккаунта?{' '}
-
+
diff --git a/src/pages/Login/index.ts b/src/pages/Login/index.ts
deleted file mode 100644
index 70459e3..0000000
--- a/src/pages/Login/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { LoginPage } from './ui/LoginPage';
\ No newline at end of file
diff --git a/src/pages/Profile/ProfilePage.tsx b/src/pages/Profile/ProfilePage.tsx
new file mode 100644
index 0000000..168208f
--- /dev/null
+++ b/src/pages/Profile/ProfilePage.tsx
@@ -0,0 +1,9 @@
+const ProfilePage = () => {
+ return (
+
+ Profile page
+
+ );
+};
+
+export default ProfilePage;
\ No newline at end of file
diff --git a/src/pages/ProjectCatalog/ProjectCatalogPage.tsx b/src/pages/ProjectCatalog/ProjectCatalogPage.tsx
new file mode 100644
index 0000000..b13e4ea
--- /dev/null
+++ b/src/pages/ProjectCatalog/ProjectCatalogPage.tsx
@@ -0,0 +1,11 @@
+import {type FC} from 'react';
+
+const ProjectCatalogPage: FC = () => {
+ return (
+
+ Catalog page
+
+ );
+};
+
+export default ProjectCatalogPage;
\ No newline at end of file
diff --git a/src/pages/Register/ui/RegisterPage.tsx b/src/pages/Register/RegisterPage.tsx
similarity index 75%
rename from src/pages/Register/ui/RegisterPage.tsx
rename to src/pages/Register/RegisterPage.tsx
index c039e77..6d4553b 100644
--- a/src/pages/Register/ui/RegisterPage.tsx
+++ b/src/pages/Register/RegisterPage.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
-import UnauthorizedLayout from '../../../widgets/UnauthorizedLayout/UnauthorizedLayout';
+import {Button} from "antd";
+import UnauthorizedLayout from "../../widgets/UnauthorizedLayout/UnauthorizedLayout.tsx";
export const RegisterPage: React.FC = () => {
const navigate = useNavigate();
@@ -18,22 +19,24 @@ export const RegisterPage: React.FC = () => {
Страница регистрации (заглушка)
-
+
Уже есть аккаунт?{' '}
-
+
diff --git a/src/pages/Register/index.ts b/src/pages/Register/index.ts
deleted file mode 100644
index 8517775..0000000
--- a/src/pages/Register/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { RegisterPage } from './ui/RegisterPage';
\ No newline at end of file
diff --git a/src/pages/Welcome/ui/WelcomePage.tsx b/src/pages/Welcome/WelcomePage.tsx
similarity index 84%
rename from src/pages/Welcome/ui/WelcomePage.tsx
rename to src/pages/Welcome/WelcomePage.tsx
index 26ebfe9..5fcac76 100644
--- a/src/pages/Welcome/ui/WelcomePage.tsx
+++ b/src/pages/Welcome/WelcomePage.tsx
@@ -1,7 +1,8 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
-import { ASSETS } from '../../../shared/config/assets';
-import UnauthorizedLayout from "../../../widgets/UnauthorizedLayout/UnauthorizedLayout.tsx";
+import {Button} from "antd";
+import UnauthorizedLayout from "../../widgets/UnauthorizedLayout/UnauthorizedLayout.tsx";
+import {ASSETS} from "../../shared/config/assets.ts";
export const WelcomePage: React.FC = () => {
const navigate = useNavigate();
@@ -32,18 +33,22 @@ export const WelcomePage: React.FC = () => {
{/* Кнопки действий */}
-
-
{/* Дополнительная информация */}
diff --git a/src/pages/Welcome/index.ts b/src/pages/Welcome/index.ts
deleted file mode 100644
index 502ce0a..0000000
--- a/src/pages/Welcome/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { WelcomePage } from './ui/WelcomePage';
\ No newline at end of file
diff --git a/src/shared/api/httpClient.ts b/src/shared/api/httpClient.ts
new file mode 100644
index 0000000..393e6d8
--- /dev/null
+++ b/src/shared/api/httpClient.ts
@@ -0,0 +1,8 @@
+import axios from "axios";
+
+export const httpClient = axios.create({
+ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8000',
+ headers: {
+ "Content-Type": "application/json",
+ },
+});
\ No newline at end of file
diff --git a/src/shared/config/.gitkeep b/src/shared/config/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/shared/lib/hooks/useMobuleMenu.ts b/src/shared/lib/hooks/useMobuleMenu.ts
new file mode 100644
index 0000000..8ce98c5
--- /dev/null
+++ b/src/shared/lib/hooks/useMobuleMenu.ts
@@ -0,0 +1,29 @@
+import {useDispatch, useSelector} from "react-redux";
+import type {RootState} from "../../../app/store";
+import {close, open, toggle} from "../../../entities/MobileMenu/mobileMenuSlice.ts";
+
+
+export const useMobileMenu = () => {
+ const dispatch = useDispatch();
+ const { isOpened } = useSelector((state: RootState) => state.mobileMenu);
+
+
+ const openMobileMenu = () => {
+ dispatch(open());
+ }
+
+ const closeMobileMenu = () => {
+ dispatch(close());
+ }
+
+ const toggleMobileMenu = () => {
+ dispatch(toggle());
+ }
+
+ return {
+ isOpened,
+ openMobileMenu,
+ closeMobileMenu,
+ toggleMobileMenu
+ }
+ }
\ No newline at end of file
diff --git a/src/shared/providers/RouterProvider.tsx b/src/shared/providers/RouterProvider.tsx
index a7b1e7c..90bcdfe 100644
--- a/src/shared/providers/RouterProvider.tsx
+++ b/src/shared/providers/RouterProvider.tsx
@@ -1,4 +1,4 @@
-import { HashRouter } from 'react-router-dom';
+import { BrowserRouter } from 'react-router-dom';
import type { FC } from 'react';
interface RouterProviderProps {
@@ -6,11 +6,10 @@ interface RouterProviderProps {
}
const RouterProvider: FC = ({ children }) => {
- // Используем HashRouter для GitHub Pages
return (
-
+
{children}
-
+
);
};
diff --git a/src/shared/ui/.gitkeep b/src/shared/ui/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/shared/ui/Logo.tsx b/src/shared/ui/Logo.tsx
new file mode 100644
index 0000000..35a6f54
--- /dev/null
+++ b/src/shared/ui/Logo.tsx
@@ -0,0 +1,22 @@
+import {type FC} from 'react';
+import {ASSETS} from "../config/assets.ts";
+
+type LogoProps = {
+ onClick?: () => void;
+};
+
+const Logo: FC = ({onClick}) => {
+ return (
+
+

+
Pretty Pet
+
+ );
+};
+
+export default Logo;
\ No newline at end of file
diff --git a/src/widgets/.gitkeep b/src/widgets/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/widgets/AuthorizedLayout/AuthorizedLayout.tsx b/src/widgets/AuthorizedLayout/AuthorizedLayout.tsx
new file mode 100644
index 0000000..1cbf5fb
--- /dev/null
+++ b/src/widgets/AuthorizedLayout/AuthorizedLayout.tsx
@@ -0,0 +1,31 @@
+import React, {type FC} from 'react';
+import AuthorizedLayoutMobileMenu from "./AuthorizedLayoutMobileMenu.tsx";
+import AuthorizedLayoutHeader from "./AuthorizedLayoutHeader.tsx";
+import AuthorizedLayoutDesktopMenu from "./AuthorizedLayoutDesktopMenu.tsx";
+
+const AuthorizedLayout: FC<{ children: React.ReactNode }> = ({children}) => {
+
+
+ return (
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+ );
+};
+
+export default AuthorizedLayout;
\ No newline at end of file
diff --git a/src/widgets/AuthorizedLayout/AuthorizedLayoutDesktopMenu.tsx b/src/widgets/AuthorizedLayout/AuthorizedLayoutDesktopMenu.tsx
new file mode 100644
index 0000000..e2e294d
--- /dev/null
+++ b/src/widgets/AuthorizedLayout/AuthorizedLayoutDesktopMenu.tsx
@@ -0,0 +1,21 @@
+import AuthorizedNavBar from "../../features/AuthorizedNavBar/AuthorizedNavBar.tsx";
+import {useNavigate} from "react-router-dom";
+import Logo from "../../shared/ui/Logo.tsx";
+import LogoutButton from "../../features/LogoutButton/LogoutButton.tsx";
+
+const AuthorizedLayoutDesktopMenu = () => {
+
+ const navigate = useNavigate();
+
+
+ return (
+
+
navigate('/authorized')} />
+
+
+
+ );
+};
+
+export default AuthorizedLayoutDesktopMenu;
\ No newline at end of file
diff --git a/src/widgets/AuthorizedLayout/AuthorizedLayoutHeader.tsx b/src/widgets/AuthorizedLayout/AuthorizedLayoutHeader.tsx
new file mode 100644
index 0000000..964dc40
--- /dev/null
+++ b/src/widgets/AuthorizedLayout/AuthorizedLayoutHeader.tsx
@@ -0,0 +1,28 @@
+import MobileMenuToggle from "../../features/MobileMenuToggle/MobileMenuToggle.tsx";
+import {ThemeToggle} from "../../features/ThemeToggle/ui/ThemeToggle.tsx";
+import {useAuth} from "../../shared/lib/hooks/useAuth.ts";
+
+const AuthorizedLayoutHeader = () => {
+
+ const {user} = useAuth();
+
+ return (
+
+ );
+};
+
+export default AuthorizedLayoutHeader;
\ No newline at end of file
diff --git a/src/widgets/AuthorizedLayout/AuthorizedLayoutMobileMenu.tsx b/src/widgets/AuthorizedLayout/AuthorizedLayoutMobileMenu.tsx
new file mode 100644
index 0000000..3c25b48
--- /dev/null
+++ b/src/widgets/AuthorizedLayout/AuthorizedLayoutMobileMenu.tsx
@@ -0,0 +1,27 @@
+import AuthorizedNavBar from "../../features/AuthorizedNavBar/AuthorizedNavBar.tsx";
+import {useMobileMenu} from "../../shared/lib/hooks/useMobuleMenu.ts";
+import Logo from "../../shared/ui/Logo.tsx";
+import {useNavigate} from "react-router-dom";
+import LogoutButton from "../../features/LogoutButton/LogoutButton.tsx";
+
+const AuthorizedLayoutMobileMenu = () => {
+
+ const {isOpened, closeMobileMenu} = useMobileMenu();
+ const navigate = useNavigate();
+
+ return (
+ closeMobileMenu()}
+ className={`
+ bg-theme-surface h-screen w-screen flex flex-col items-center justify-between gap-12 sm:hidden absolute
+ transition-transform duration-300 py-12 z-2
+ ${isOpened ? 'translate-x-0' : 'translate-x-full'}
+ `}
+ >
+
navigate('/authorized')}/>
+
+
+
+ );
+};
+
+export default AuthorizedLayoutMobileMenu;
\ No newline at end of file
diff --git a/src/widgets/UnauthorizedLayout/UnauthorizedLayout.tsx b/src/widgets/UnauthorizedLayout/UnauthorizedLayout.tsx
index 15c5b99..47bb25d 100644
--- a/src/widgets/UnauthorizedLayout/UnauthorizedLayout.tsx
+++ b/src/widgets/UnauthorizedLayout/UnauthorizedLayout.tsx
@@ -1,7 +1,7 @@
import {ThemeToggle} from "../../features/ThemeToggle/ui/ThemeToggle.tsx";
-import {ASSETS} from "../../shared/config/assets";
import type {FC} from "react";
import {useNavigate} from "react-router-dom";
+import Logo from "../../shared/ui/Logo.tsx";
interface UnauthorizedLayoutProps {
children: React.ReactNode;
@@ -15,15 +15,7 @@ const UnauthorizedLayout:FC = ({children}) => {
{/* Header с логотипом и переключателем темы */}
- navigate('/')}>
-

-
Pretty Pet
-
+ navigate('/')}/>