diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 9a0e786..fbf1bfb 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,6 +8,8 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
+ "axios": "^1.7.7",
+ "jwt-decode": "^4.0.0",
"vue": "^3.5.12",
"vue-router": "^4.4.5",
"vuex": "^4.1.0"
@@ -1337,6 +1339,21 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz",
"integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg=="
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.7.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
+ "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/birpc": {
"version": "0.2.19",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz",
@@ -1413,6 +1430,17 @@
}
]
},
+ "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==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -1510,6 +1538,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.57",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.57.tgz",
@@ -1611,6 +1647,38 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
@@ -1822,6 +1890,14 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jwt-decode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+ "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/kolorist": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
@@ -1851,6 +1927,25 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
+ "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==",
+ "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==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
@@ -2032,6 +2127,11 @@
"node": "^10 || ^12 || >=14"
}
},
+ "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=="
+ },
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 9f19332..3d52cfa 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,6 +9,8 @@
"preview": "vite preview"
},
"dependencies": {
+ "axios": "^1.7.7",
+ "jwt-decode": "^4.0.0",
"vue": "^3.5.12",
"vue-router": "^4.4.5",
"vuex": "^4.1.0"
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index f78a892..6971cc5 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,9 +1,29 @@
-
-
+
+
+
+
diff --git a/frontend/src/assets/css/main.css b/frontend/src/assets/css/main.css
index 8abeeaa..16b33c6 100644
--- a/frontend/src/assets/css/main.css
+++ b/frontend/src/assets/css/main.css
@@ -2,7 +2,6 @@
#app {
max-width: 1280px;
- min-height: 100vh;
margin: 0 auto;
font-weight: normal;
background-color: #FFFFFF;
@@ -13,4 +12,9 @@ body {
background-color: #D9D9D9;
}
+html, body {
+ height: 100%;
+ margin: 0;
+}
+
diff --git a/frontend/src/assets/svg/dot.svg b/frontend/src/assets/svg/dot.svg
new file mode 100644
index 0000000..4a50103
--- /dev/null
+++ b/frontend/src/assets/svg/dot.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/src/assets/svg/sidebar/right.svg b/frontend/src/assets/svg/sidebar/right.svg
new file mode 100644
index 0000000..6b90137
--- /dev/null
+++ b/frontend/src/assets/svg/sidebar/right.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/assets/svg/spinner.svg b/frontend/src/assets/svg/spinner.svg
new file mode 100644
index 0000000..3f02fa3
--- /dev/null
+++ b/frontend/src/assets/svg/spinner.svg
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/AdminSideBar.vue b/frontend/src/components/AdminSideBar.vue
new file mode 100644
index 0000000..038ffbf
--- /dev/null
+++ b/frontend/src/components/AdminSideBar.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/TheFooter.vue b/frontend/src/components/TheFooter.vue
new file mode 100644
index 0000000..78e00fe
--- /dev/null
+++ b/frontend/src/components/TheFooter.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/TheHeader.vue b/frontend/src/components/TheHeader.vue
index 407590a..16247c2 100644
--- a/frontend/src/components/TheHeader.vue
+++ b/frontend/src/components/TheHeader.vue
@@ -43,6 +43,10 @@ export default {
.header {
width: 100%;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+ position: sticky;
+ top: 0;
+ background-color: #FFFFFF;
+ z-index: 1000;
}
.content {
diff --git a/frontend/src/components/TheNavbar.vue b/frontend/src/components/TheNavbar.vue
index 30dd193..3a2ff2c 100644
--- a/frontend/src/components/TheNavbar.vue
+++ b/frontend/src/components/TheNavbar.vue
@@ -4,10 +4,58 @@
-
+
+
Категории
+
+
+
+
Чаще всего арендуют
+
+
+
+
Отзывы
+
+
+
+
Условия аренды
+
+
+
+
Оплата и доставка
+
+
+
+
Контакты
+
+
\ No newline at end of file
diff --git a/frontend/src/components/UploadProgress.vue b/frontend/src/components/UploadProgress.vue
new file mode 100644
index 0000000..5e3d2a7
--- /dev/null
+++ b/frontend/src/components/UploadProgress.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
diff --git a/frontend/src/components/modals/LoginModal.vue b/frontend/src/components/modals/LoginModal.vue
index 80dd26e..c9c03b0 100644
--- a/frontend/src/components/modals/LoginModal.vue
+++ b/frontend/src/components/modals/LoginModal.vue
@@ -33,18 +33,18 @@
Пароль
-
-
+
@@ -75,17 +75,19 @@
+
+
+
\ No newline at end of file
diff --git a/frontend/src/js/axiosConfig.js b/frontend/src/js/axiosConfig.js
new file mode 100644
index 0000000..b69bfd4
--- /dev/null
+++ b/frontend/src/js/axiosConfig.js
@@ -0,0 +1,38 @@
+import axios from 'axios';
+import {getAccessToken, logoutUser, refreshToken} from "@/services/authService.js";
+
+// Устанавливаем интерцептор для автоматической установки заголовка
+axios.interceptors.request.use(
+ async (config) => {
+ const token = getAccessToken();
+ if (token) {
+ config.headers['Authorization'] = `Bearer ${token}`;
+ }
+ return config;
+ },
+ (error) => {
+ return Promise.reject(error);
+ }
+);
+
+// Интерцептор ответа для обновления токена при 401 ошибке
+axios.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+ const originalRequest = error.config;
+
+ if (error.response.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true;
+ try {
+ const newToken = await refreshToken();
+ axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
+ originalRequest.headers['Authorization'] = `Bearer ${newToken}`;
+ return axios(originalRequest);
+ } catch (error) {
+ logoutUser();
+ window.location.href = '/';
+ }
+ }
+ return Promise.reject(error);
+ }
+);
\ No newline at end of file
diff --git a/frontend/src/js/router.js b/frontend/src/js/router.js
index 29788a1..28f07fb 100644
--- a/frontend/src/js/router.js
+++ b/frontend/src/js/router.js
@@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
+import AdminOrderView from "@/views/AdminOrderView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -9,6 +10,11 @@ const router = createRouter({
name: 'home',
component: HomeView,
},
+ {
+ path: '/admin/orders',
+ name:'admin-orders',
+ component: AdminOrderView,
+ }
],
})
diff --git a/frontend/src/js/store.js b/frontend/src/js/store.js
index aab570e..ea3831c 100644
--- a/frontend/src/js/store.js
+++ b/frontend/src/js/store.js
@@ -3,6 +3,7 @@ import { createStore } from 'vuex';
const store = createStore({
state: {
isAuthenticated: false,
+ isAdmin: false,
},
mutations: {
login(state) {
@@ -14,6 +15,7 @@ const store = createStore({
},
getters: {
isAuthenticated: (state) => state.isAuthenticated,
+ isAdmin: (state) => state.isAdmin,
},
});
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 1401d3a..cf3d0a4 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -1,11 +1,14 @@
import './assets/css/main.css'
+import './js/axiosConfig.js'
import { createApp } from 'vue'
import App from './App.vue'
import router from './js/router.js'
+import store from './js/store.js'
const app = createApp(App)
app.use(router)
+app.use(store)
app.mount('#app')
diff --git a/frontend/src/services/authService.js b/frontend/src/services/authService.js
new file mode 100644
index 0000000..a9a1712
--- /dev/null
+++ b/frontend/src/services/authService.js
@@ -0,0 +1,76 @@
+import axios from 'axios';
+import {jwtDecode} from "jwt-decode";
+import store from "@/js/store.js";
+
+const API_URL = 'http://localhost:3000/api/auth';
+
+export const register = async (userData) => {
+ try{
+ const response = await axios.post(`${API_URL}/register/client`, userData);
+ return { data: response.data, error: null};
+ } catch (error) {
+ const errorMessage = error.response
+ ? error.response.data // Если есть ответ от сервера
+ : error.message || 'Ошибка соединения';
+ return { data: null, error: errorMessage}
+ }
+};
+
+export const login = async (userData) => {
+ try{
+ const response = await axios.post(`${API_URL}/token`, userData);
+ return { data: response.data, error: null}
+ } catch (error) {
+ const errorMessage = error.response
+ ? error.response.data // Если есть ответ от сервера
+ : error.message || 'Ошибка соединения';
+ return { data: null, error: errorMessage}
+ }
+};
+
+export const storeUserInfo = (token) => {
+ storeAccessToken(token);
+ const decodedToken = jwtDecode(token);
+ const isAdmin = (decodedToken.role === "admin") || false;
+ const isAuthenticated = true;
+
+ localStorage.setItem('isAdmin', isAdmin.toString());
+ localStorage.setItem('isAuthenticated', isAuthenticated.toString());
+}
+
+export const isUserAdmin = () => {
+ return localStorage.getItem('isAdmin') === 'true';
+};
+
+export const isUserAuthenticated = () => {
+ return localStorage.getItem('isAuthenticated') === 'true';
+};
+
+export const storeAccessToken = (token) => {
+ localStorage.setItem('access_token', token);
+};
+
+export const getAccessToken = () => {
+ return localStorage.getItem('access_token');
+};
+
+export const logoutUser = () => {
+ localStorage.removeItem('access_token');
+ localStorage.removeItem('isAdmin');
+ localStorage.removeItem('isAuthenticated');
+};
+
+export const refreshToken = async () => {
+ try {
+ const response = await axios.post('/api/auth/token/refresh', null, {
+ withCredentials: true // Указывает, что куки (refresh-токен) должны быть отправлены с запросом
+ });
+ const { access_token } = response.data;
+ storeAccessToken(access_token);
+ storeUserInfo(access_token);
+ return access_token;
+ } catch (error) {
+ console.error('Ошибка обновления токена:', error);
+ throw error;
+ }
+};
diff --git a/frontend/src/views/AdminOrderView.vue b/frontend/src/views/AdminOrderView.vue
new file mode 100644
index 0000000..92fd7be
--- /dev/null
+++ b/frontend/src/views/AdminOrderView.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue
index 37f2386..de20965 100644
--- a/frontend/src/views/HomeView.vue
+++ b/frontend/src/views/HomeView.vue
@@ -1,8 +1,9 @@
- Hello
+