mirror of
				https://github.com/creativetimofficial/vue-material-kit.git
				synced 2025-11-01 05:35:39 +08:00 
			
		
		
		
	
						commit
						8d7afcf5e0
					
				| @ -5,7 +5,7 @@ import { useWindowsWidth } from "../../assets/js/useWindowsWidth"; | |||||||
| 
 | 
 | ||||||
| // images | // images | ||||||
| import ArrDark from "@/assets/img/down-arrow-dark.svg"; | import ArrDark from "@/assets/img/down-arrow-dark.svg"; | ||||||
| import downArrow from "@/assets/img/down-arrow.svg"; | 
 | ||||||
| import DownArrWhite from "@/assets/img/down-arrow-white.svg"; | import DownArrWhite from "@/assets/img/down-arrow-white.svg"; | ||||||
| 
 | 
 | ||||||
| const isAuthenticated = computed(() => !!sessionStorage.getItem('access_token')); // Computed property to check if the user is authenticated | const isAuthenticated = computed(() => !!sessionStorage.getItem('access_token')); // Computed property to check if the user is authenticated | ||||||
| @ -260,6 +260,22 @@ watch( | |||||||
|                       </RouterLink> |                       </RouterLink> | ||||||
| 
 | 
 | ||||||
|                     </div> |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <div v-if="isAuthenticated" class="position-relative"> | ||||||
|  |                       <div | ||||||
|  |                         class="dropdown-header text-dark font-weight-bolder d-flex align-items-center px-1" | ||||||
|  |                       > | ||||||
|  |                         Мои проекты | ||||||
|  |                       </div> | ||||||
|  |                       <RouterLink | ||||||
|  |                         :to="{ name: 'myprojects' }" | ||||||
|  |                         class="dropdown-item border-radius-md" | ||||||
|  |                       > | ||||||
|  |                         <span>Мои проекты</span> | ||||||
|  |                       </RouterLink> | ||||||
|  | 
 | ||||||
|  |                     </div> | ||||||
|  | 
 | ||||||
|                   </div> |                   </div> | ||||||
|                 </div> |                 </div> | ||||||
|               </div> |               </div> | ||||||
| @ -312,9 +328,9 @@ watch( | |||||||
|                       class="dropdown-item py-2 ps-3 border-radius-md" |                       class="dropdown-item py-2 ps-3 border-radius-md" | ||||||
|                       href="/ViewMyProfile" |                       href="/ViewMyProfile" | ||||||
|                     > |                     > | ||||||
|                       <h6 class="dropdown-header text-dark font-weight-bolder d-flex justify-content-cente align-items-center p-0"> Мой Профиль </h6> | 
 | ||||||
|                       <span class="text-sm">Рассказ о том, какой я классный</span> |  | ||||||
|                     </a> |                     </a> | ||||||
|  | 
 | ||||||
|                     <a |                     <a | ||||||
|                       class="dropdown-item py-2 ps-3 border-radius-md" |                       class="dropdown-item py-2 ps-3 border-radius-md" | ||||||
|                       href="/CreateProject" |                       href="/CreateProject" | ||||||
| @ -325,7 +341,7 @@ watch( | |||||||
|                         Создать проект |                         Создать проект | ||||||
|                       </h6> |                       </h6> | ||||||
|                       <span class="text-sm" |                       <span class="text-sm" | ||||||
|                         >Чтобы стать ещё более классным</span |                         >Страница добавления проекта</span | ||||||
|                       > |                       > | ||||||
|                     </a> |                     </a> | ||||||
|                   </li> |                   </li> | ||||||
| @ -339,9 +355,10 @@ watch( | |||||||
|                     <h6 class="dropdown-header text-dark font-weight-bolder d-flex justify-content-cente align-items-center p-0"> |                     <h6 class="dropdown-header text-dark font-weight-bolder d-flex justify-content-cente align-items-center p-0"> | ||||||
|                       Мой профиль |                       Мой профиль | ||||||
|                     </h6> |                     </h6> | ||||||
|                     <span class="text-sm">Рассказ о том, какой я классный</span> | 
 | ||||||
|                   </a> |                   </a> | ||||||
|                 </div> |                 </div> | ||||||
|  |                  | ||||||
|               </div> |               </div> | ||||||
|                |                | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ import ViewMyProfile from "../views/LandingPages/Profile/AdmireProfile.vue"; | |||||||
| import EditMyProfile from "../views/LandingPages/Profile/EditProfile.vue"; | import EditMyProfile from "../views/LandingPages/Profile/EditProfile.vue"; | ||||||
| import CreateProject from "../views/LandingPages/Project/AddProject.vue"; | import CreateProject from "../views/LandingPages/Project/AddProject.vue"; | ||||||
| import EditProject from "../views/LandingPages/Project/EditProject.vue"; | import EditProject from "../views/LandingPages/Project/EditProject.vue"; | ||||||
|  | import MyProjects from "../views/LandingPages/Project/MyProjects.vue"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| const router = createRouter({ | const router = createRouter({ | ||||||
| @ -38,6 +39,12 @@ const router = createRouter({ | |||||||
|       component: Projects |       component: Projects | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|  |     { | ||||||
|  |       path: '/myprojects', | ||||||
|  |       name: 'myprojects', | ||||||
|  |       component: MyProjects | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|     { |     { | ||||||
|       path: '/CreateProject', |       path: '/CreateProject', | ||||||
|       name: 'createproject', |       name: 'createproject', | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| import { onMounted, onUnmounted } from "vue"; | import { onMounted, onUnmounted } from "vue"; | ||||||
| import axios from 'axios'; | import axios from 'axios'; | ||||||
| import { ref } from "vue"; | import { ref } from "vue"; | ||||||
|  | import NavbarDefault from "../../../examples/navbars/NavbarDefault.vue"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| const searchQuery = ref(''); | const searchQuery = ref(''); | ||||||
| @ -29,7 +30,7 @@ onMounted(() => { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
| 
 | <NavbarDefault /> | ||||||
|   <div> |   <div> | ||||||
|      |      | ||||||
|     <h2 class="result-header">Найдено людей: {{ searchResultUsers.length}} </h2> |     <h2 class="result-header">Найдено людей: {{ searchResultUsers.length}} </h2> | ||||||
| @ -37,7 +38,7 @@ onMounted(() => { | |||||||
|       <div class="result-card" v-for="user in searchResultUsers" :key="user.id"> |       <div class="result-card" v-for="user in searchResultUsers" :key="user.id"> | ||||||
|         <h3>{{ user.username }} with id {{ user.id }}</h3> |         <h3>{{ user.username }} with id {{ user.id }}</h3> | ||||||
|         <p>{{ user.email }}</p> |         <p>{{ user.email }}</p> | ||||||
|         <a :href="`http://somebodyhire.me/profile/${user.id}`">Страница пользователя</a> |         <a :href="`/profile/${user.id}`">Страница пользователя</a> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  | |||||||
| @ -4,9 +4,6 @@ import { onMounted, ref, computed } from "vue"; | |||||||
| import NavbarDefault from "../../../examples/navbars/NavbarDefault.vue"; | import NavbarDefault from "../../../examples/navbars/NavbarDefault.vue"; | ||||||
| import { useRouter } from "vue-router"; | import { useRouter } from "vue-router"; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| const isAuthenticated = computed(() => !!sessionStorage.getItem('access_token')); | const isAuthenticated = computed(() => !!sessionStorage.getItem('access_token')); | ||||||
| const userId = computed(() => sessionStorage.getItem('user_id')); | const userId = computed(() => sessionStorage.getItem('user_id')); | ||||||
| const loggedUserName = computed(() => sessionStorage.getItem('username')); | const loggedUserName = computed(() => sessionStorage.getItem('username')); | ||||||
| @ -14,9 +11,8 @@ const token = computed(() => sessionStorage.getItem('access_token')); | |||||||
| 
 | 
 | ||||||
| const profileData = ref([]); | const profileData = ref([]); | ||||||
| const router = useRouter(); | const router = useRouter(); | ||||||
| 
 |  | ||||||
| const debugText = ref(''); | const debugText = ref(''); | ||||||
| 
 | const selectedImage = ref(null); | ||||||
| 
 | 
 | ||||||
| const getProfile = async () => { | const getProfile = async () => { | ||||||
|     const profileDataRecieved = await axios.get(`http://somebodyhire.me/api/profile/${userId.value}/`); |     const profileDataRecieved = await axios.get(`http://somebodyhire.me/api/profile/${userId.value}/`); | ||||||
| @ -25,21 +21,20 @@ const getProfile = async () => { | |||||||
| 
 | 
 | ||||||
| const processProfileData = (data) => { | const processProfileData = (data) => { | ||||||
|     return { |     return { | ||||||
|         ...data, |         name: data.name || '', | ||||||
|         name: data.name || '🤷 No Name Provided', |         email: data.email || '', | ||||||
|         location: data.location || '🌍 No Location Provided', |         username: data.username || '', | ||||||
|         short_intro: data.short_intro || '📝 No Short Intro Provided', |         location: data.location || '', | ||||||
|         bio: data.bio || '📘 No Bio Provided', |         short_intro: data.short_intro || '', | ||||||
|         profile_image: data.profile_image || '📷 No Image Provided', |         bio: data.bio || '', | ||||||
|         social_github: data.social_github || '👨💻 No Github Provided', |         social_github: data.social_github || '', | ||||||
|         social_twitter: data.social_twitter || '🐦 No Twitter Provided', |         social_twitter: data.social_twitter || '', | ||||||
|         social_vk: data.social_vk || '🔵 No VK Provided', |         social_vk: data.social_vk || '', | ||||||
|         social_youtube: data.social_youtube || '▶️ No YouTube Provided', |         social_youtube: data.social_youtube || '', | ||||||
|         social_website: data.social_website || '🌐 No Website Provided', |         social_website: data.social_website || '', | ||||||
|     }; |     }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // Axios request and response interceptors |  | ||||||
| axios.interceptors.request.use((request) => { | axios.interceptors.request.use((request) => { | ||||||
|   debugText.value += '\n\nRequest:\n' + JSON.stringify(request, null, 2); |   debugText.value += '\n\nRequest:\n' + JSON.stringify(request, null, 2); | ||||||
|   return request; |   return request; | ||||||
| @ -53,19 +48,34 @@ axios.interceptors.response.use((response) => { | |||||||
|   return Promise.reject(error); |   return Promise.reject(error); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const updateProfile = async () => { | const onFileChange = (event) => { | ||||||
|     try { |     selectedImage.value = event.target.files[0]; | ||||||
|         const token = computed(() => sessionStorage.getItem('access_token')); |     debugText.value = `Selected image: ${selectedImage.value.name}`; | ||||||
|         debugText.value = `Type of token: ${typeof token.value}, Value of token: ${token.value}`; |  | ||||||
|         const headers = { 'Authorization': `Bearer ${token.value}` }; |  | ||||||
|         await axios.put(`http://somebodyhire.me/api/profile/${userId.value}/`, profileData.value, { headers }); |  | ||||||
|         router.push('/ViewMyProfile'); |  | ||||||
|     } catch (error) { |  | ||||||
|         debugText.value = `Error: ${JSON.stringify(error, null, 2)}`; |  | ||||||
|         console.error(error); |  | ||||||
|     } |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | const updateProfile = async () => { | ||||||
|  |   try { | ||||||
|  |     const tokenValue = token.value; | ||||||
|  |     const headers = { 'Authorization': `Bearer ${tokenValue}` }; | ||||||
|  | 
 | ||||||
|  |     // Create a new FormData object | ||||||
|  |     const formData = new FormData(); | ||||||
|  | 
 | ||||||
|  |     // Append the profile data | ||||||
|  |     Object.entries(profileData.value).forEach(([key, value]) => { | ||||||
|  |       formData.append(key, value); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     // Append the image file | ||||||
|  |     formData.append('image', selectedImage.value); | ||||||
|  | 
 | ||||||
|  |     await axios.patch(`http://somebodyhire.me/api/profile/${userId.value}/`, formData, { headers }); | ||||||
|  |     // router.push('/ViewMyProfile'); | ||||||
|  |   } catch (error) { | ||||||
|  |     debugText.value = `Error: ${JSON.stringify(error, null, 2)}`; | ||||||
|  |     console.error(error); | ||||||
|  |   } | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| const cancelUpdate = () => { | const cancelUpdate = () => { | ||||||
|     router.push('/ViewMyProfile'); |     router.push('/ViewMyProfile'); | ||||||
| @ -74,42 +84,30 @@ const cancelUpdate = () => { | |||||||
| onMounted(async() => { | onMounted(async() => { | ||||||
|     await getProfile(); |     await getProfile(); | ||||||
| }); | }); | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <script> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </script> |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| <template> | <template> | ||||||
|     <NavbarDefault /> |     <NavbarDefault /> | ||||||
|     <div class="profile-container"> |     <div class="profile-container"> | ||||||
|       <h1>Профиль пользователя {{ loggedUserName }}</h1> |         <h1>User Profile: {{ loggedUserName }}</h1> | ||||||
|         <textarea readonly v-model="debugText"></textarea> |         <textarea readonly v-model="debugText"></textarea> | ||||||
|  |         <input type="file" accept="image/*" @change="onFileChange"> | ||||||
|         <input type="text" v-model="profileData.username" placeholder="Username"> |         <input type="text" v-model="profileData.username" placeholder="Username"> | ||||||
|         <input type="email" v-model="profileData.email" placeholder="Email"> |         <input type="email" v-model="profileData.email" placeholder="Email"> | ||||||
|         <input type="text" v-model="profileData.name" placeholder="Имя"> |         <input type="text" v-model="profileData.name" placeholder="Name"> | ||||||
|         <input type="text" v-model="profileData.short_intro" placeholder="Краткое описание"> |         <input type="text" v-model="profileData.short_intro" placeholder="Short Introduction"> | ||||||
|         <textarea v-model="profileData.bio" placeholder="Биография"></textarea> |         <textarea v-model="profileData.bio" placeholder="Biography"></textarea> | ||||||
|         <textarea v-model="profileData.profile_image" placeholder="Ссылка на изображение"></textarea> |         <textarea v-model="profileData.social_github" placeholder="GitHub Link"></textarea> | ||||||
|         <textarea v-model="profileData.social_github" placeholder="Ссылка на GitHub"></textarea> |         <textarea v-model="profileData.social_twitter" placeholder="Twitter Link"></textarea> | ||||||
|         <textarea v-model="profileData.social_twitter" placeholder="Ссылка на Twitter"></textarea> |         <textarea v-model="profileData.social_vk" placeholder="VK Link"></textarea> | ||||||
|         <textarea v-model="profileData.social_vk" placeholder="Ссылка на VK"></textarea> |         <textarea v-model="profileData.social_youtube" placeholder="YouTube Link"></textarea> | ||||||
|         <textarea v-model="profileData.social_youtube" placeholder="Ссылка на YouTube"></textarea> |         <textarea v-model="profileData.social_website" placeholder="Website Link"></textarea> | ||||||
|         <textarea v-model="profileData.social_website" placeholder="Ссылка на сайт"></textarea> |  | ||||||
|         <button @click="updateProfile" class="btn-submit">Submit</button> |         <button @click="updateProfile" class="btn-submit">Submit</button> | ||||||
|         <button @click="cancelUpdate" class="btn-cancel">Cancel</button> |         <button @click="cancelUpdate" class="btn-cancel">Cancel</button> | ||||||
|     </div> |     </div> | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| <style scoped> | <style scoped> | ||||||
| .profile-container { | .profile-container { | ||||||
|   display: flex; |   display: flex; | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
| import axios from 'axios'; | import axios from 'axios'; | ||||||
| import { onMounted, ref } from "vue"; | import { onMounted, ref } from "vue"; | ||||||
| import { useRoute } from "vue-router"; | import { useRoute } from "vue-router"; | ||||||
| 
 | import NavbarDefault from '../../../examples/navbars/NavbarDefault.vue'; | ||||||
| 
 | 
 | ||||||
| const profileId = ref(null); | const profileId = ref(null); | ||||||
| const route = useRoute(); | const route = useRoute(); | ||||||
| @ -27,17 +27,45 @@ const getProfile = async () => { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <div> |   <NavbarDefault /> | ||||||
|       <h1>Профиль пользователя номер: {{ profileData.id }}</h1> |   <div class="profile-container"> | ||||||
|  |     <h1>Профиль пользователя {{ loggedUserName }}</h1> | ||||||
|       <h2>{{ profileData.username }}</h2> |       <h2>{{ profileData.username }}</h2> | ||||||
|         <p>{{ profileData.email }}</p> |       <p>{{ profileData.email }}</p> | ||||||
|           |       <P>Имя: {{ profileData.name }}</P> | ||||||
|  |       <p>Местоположение: {{ profileData.location }}</p> | ||||||
|  |       <p>Краткое описание: {{ profileData.short_intro }}</p> | ||||||
|  |       <p>Биография: {{ profileData.bio }}</p> | ||||||
|  |       <p>Ссылка на изображение: {{ profileData.profile_image }}</p> | ||||||
|  |       <p>Ссылка на GitHub: {{ profileData.social_github }}</p> | ||||||
|  |       <p>Ссылка на Twitter: {{ profileData.social_twitter }}</p> | ||||||
|  |       <p>Ссылка на VK: {{ profileData.social_vk }}</p> | ||||||
|  |       <p>Ссылка на YouTube: {{ profileData.social_youtube }}</p> | ||||||
|  |       <p>Ссылка на сайт: {{ profileData.social_website }}</p> | ||||||
|        |        | ||||||
|     </div> | 
 | ||||||
|   </template>  |      | ||||||
|  |   </div> | ||||||
|  | </template>  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
| </style> | .profile-container { | ||||||
|    | display: flex; | ||||||
|  | flex-direction: column; | ||||||
|  | align-items: center; | ||||||
|  | width: 80%; | ||||||
|  | margin: auto; | ||||||
|  | padding: 20px; | ||||||
|  | box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .profile-container img { | ||||||
|  | width: 100px; | ||||||
|  | height: 100px; | ||||||
|  | border-radius: 50%; | ||||||
|  | object-fit: cover; | ||||||
|  | margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @ -2,7 +2,7 @@ | |||||||
| import { onMounted, onUnmounted } from "vue"; | import { onMounted, onUnmounted } from "vue"; | ||||||
| import axios from 'axios'; | import axios from 'axios'; | ||||||
| import { ref } from "vue"; | import { ref } from "vue"; | ||||||
| 
 | import NavbarDefault from "../../../examples/navbars/NavbarDefault.vue"; | ||||||
| 
 | 
 | ||||||
| const searchQuery = ref(''); | const searchQuery = ref(''); | ||||||
| const searchResultProjects = ref([]); | const searchResultProjects = ref([]); | ||||||
| @ -29,14 +29,14 @@ onMounted(() => { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
| 
 | <NavbarDefault  /> | ||||||
|   <div> |   <div> | ||||||
|     <h2 class="result-header">Найдено проектов: {{ searchResultProjects.length}} </h2> |     <h2 class="result-header">Найдено проектов: {{ searchResultProjects.length}} </h2> | ||||||
|     <div class="result-grid"> |     <div class="result-grid"> | ||||||
|       <div class="result-card" v-for="project in searchResultProjects" :key="project.id"> |       <div class="result-card" v-for="project in searchResultProjects" :key="project.id"> | ||||||
|         <h3>{{ project.title }} with ID {{ project.id }}</h3> |         <h3>{{ project.title }} with ID {{ project.id }}</h3> | ||||||
|         <p>{{ project.description }}</p> |         <p>{{ project.description }}</p> | ||||||
|         <a :href="`http://somebodyhire.me/project/${project.id}`">Страница проекта</a> |         <a :href="`/project/${project.id}`">Страница проекта</a> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|      |      | ||||||
|  | |||||||
| @ -0,0 +1,181 @@ | |||||||
|  | <script setup> | ||||||
|  | import axios from 'axios'; | ||||||
|  | import { onMounted, ref, computed } from "vue"; | ||||||
|  | import NavbarDefault from "../../../examples/navbars/NavbarDefault.vue"; | ||||||
|  | import { useRouter } from "vue-router"; | ||||||
|  | import { useRoute } from 'vue-router'; | ||||||
|  | 
 | ||||||
|  | const isAuthenticated = computed(() => !!sessionStorage.getItem('access_token')); | ||||||
|  | const userId = computed(() => sessionStorage.getItem('user_id')); | ||||||
|  | const loggedUserName = computed(() => sessionStorage.getItem('username')); | ||||||
|  | const token = computed(() => sessionStorage.getItem('access_token')); | ||||||
|  | 
 | ||||||
|  | const projectData = ref([]); | ||||||
|  | const router = useRouter(); | ||||||
|  | const debugText = ref(''); | ||||||
|  | const projectId = ref(null); | ||||||
|  | const route = useRoute(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // const getProfile = async () => { | ||||||
|  | //     const profileDataRecieved = await axios.get(`http://somebodyhire.me/api/profile/${userId.value}/`); | ||||||
|  | //     profileData.value = processProfileData(profileDataRecieved.data); | ||||||
|  | // }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | axios.interceptors.request.use((request) => { | ||||||
|  |   debugText.value += '\n\nRequest:\n' + JSON.stringify(request, null, 2); | ||||||
|  |   return request; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | axios.interceptors.response.use((response) => { | ||||||
|  |   debugText.value += '\n\nResponse:\n' + JSON.stringify(response, null, 2); | ||||||
|  |   return response; | ||||||
|  | }, (error) => { | ||||||
|  |   debugText.value += '\n\nResponse Error:\n' + JSON.stringify(error.toJSON(), null, 2); | ||||||
|  |   return Promise.reject(error); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const getProject = async () => { | ||||||
|  |     try { | ||||||
|  |         const projectDataRecieved = await axios.get(`http://somebodyhire.me/api/projects/${projectId.value}/`); | ||||||
|  |         projectData.value = projectDataRecieved.data; | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error('There was an error fetching the project data', error); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // const updateProfile = async () => { | ||||||
|  | //     try { | ||||||
|  | //         const token = computed(() => sessionStorage.getItem('access_token')); | ||||||
|  | //         debugText.value = `Type of token: ${typeof token.value}, Value of token: ${token.value}`; | ||||||
|  | //         const headers = { 'Authorization': `Bearer ${token.value}` }; | ||||||
|  | //         await axios.patch(`http://somebodyhire.me/api/profile/${userId.value}/`, profileData.value, { headers }); | ||||||
|  | //         router.push('/ViewMyProfile'); | ||||||
|  | //     } catch (error) { | ||||||
|  | //         debugText.value = `Error: ${JSON.stringify(error, null, 2)}`; | ||||||
|  | //         console.error(error); | ||||||
|  | //     } | ||||||
|  | // }; | ||||||
|  | 
 | ||||||
|  | const updateProject = async () => { | ||||||
|  |     try { | ||||||
|  |         const headers = { 'Authorization': `Bearer ${token.value}` }; | ||||||
|  |         const data = { | ||||||
|  |             title: projectData.value.title, | ||||||
|  |             description: projectData.value.description, | ||||||
|  |             demo_link: projectData.value.demo_link, | ||||||
|  |             source_link: projectData.value.source_link, | ||||||
|  |             tags: projectData.value.tags, | ||||||
|  | 
 | ||||||
|  |         }; | ||||||
|  |         const response = await axios.patch(`http://somebodyhire.me/api/projects/${projectId.value}/`, data, { headers }); | ||||||
|  |         router.push(`/project/${response.data.id}`); | ||||||
|  |     } catch (error) { | ||||||
|  |         debugText.value = `Error: ${JSON.stringify(error, null, 2)}`; | ||||||
|  |         console.error(error); | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const cancelUpdate = () => { | ||||||
|  |     router.push('/myprojects'); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | onMounted(async() => { | ||||||
|  |     projectId.value = route.params.id; | ||||||
|  |     await getProject(); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |     <NavbarDefault /> | ||||||
|  |     <div class="profile-container"> | ||||||
|  |         <H1> Страница Редактирования Проекта {{ projectId }}</H1> | ||||||
|  |         <div v-if="!isAuthenticated"> | ||||||
|  |             <h1>Вы не авторизованы</h1> | ||||||
|  |         </div> | ||||||
|  |         <div v-else> | ||||||
|  |             <div v-if = "userId == projectData.owner"> | ||||||
|  |         <h1>User Profile: {{ loggedUserName }}</h1> | ||||||
|  |         <textarea readonly v-model="debugText"></textarea> | ||||||
|  |         <input type="text" v-model="projectData.title" placeholder="Title"> | ||||||
|  |         <input type="text" v-model="projectData.description" placeholder="Description"> | ||||||
|  |         <textarea v-model="projectData.demo_link" placeholder="Demo link"></textarea> | ||||||
|  |         <textarea v-model="projectData.source_link" placeholder="Source code link"></textarea> | ||||||
|  |         <textarea v-model="projectData.tags" placeholder="Tags"></textarea> | ||||||
|  |         <button @click="updateProject" class="btn-submit">Update</button> | ||||||
|  |         <button @click="cancelUpdate" class="btn-cancel">Cancel</button> | ||||||
|  |             </div> | ||||||
|  |             <div v-else> | ||||||
|  |                 <h1>Вы не являетесь владельцем проекта</h1> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .profile-container { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   align-items: center; | ||||||
|  |   width: 80%; | ||||||
|  |   margin: auto; | ||||||
|  |   padding: 20px; | ||||||
|  |   box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.1); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .profile-container img { | ||||||
|  |   width: 100px; | ||||||
|  |   height: 100px; | ||||||
|  |   border-radius: 50%; | ||||||
|  |   object-fit: cover; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .profile-container input, .profile-container textarea { | ||||||
|  |   width: 100%; /* Make inputs and textareas take up the full width of the container */ | ||||||
|  |   padding: 10px; /* Add some padding */ | ||||||
|  |   margin-bottom: 15px; /* Add some margin */ | ||||||
|  |   box-sizing: border-box; /* Ensure padding doesn't affect final dimensions */ | ||||||
|  |   border: 1px solid #ccc; /* Add a border */ | ||||||
|  |   border-radius: 5px; /* Add rounded corners */ | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* Style for smaller screens */ | ||||||
|  | @media (max-width: 768px) { | ||||||
|  |   .profile-container { | ||||||
|  |     width: 95%; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .btn-submit { | ||||||
|  |     color: #fff; | ||||||
|  |     background-color: #4CAF50; | ||||||
|  |     border: none; | ||||||
|  |     padding: 15px 32px; | ||||||
|  |     text-align: center; | ||||||
|  |     text-decoration: none; | ||||||
|  |     display: inline-block; | ||||||
|  |     font-size: 16px; | ||||||
|  |     margin: 4px 2px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     border-radius: 5px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .btn-cancel { | ||||||
|  |     color: #fff; | ||||||
|  |     background-color: #f44336; | ||||||
|  |     border: none; | ||||||
|  |     padding: 15px 32px; | ||||||
|  |     text-align: center; | ||||||
|  |     text-decoration: none; | ||||||
|  |     display: inline-block; | ||||||
|  |     font-size: 16px; | ||||||
|  |     margin: 4px 2px; | ||||||
|  |     cursor: pointer; | ||||||
|  |     border-radius: 5px; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										102
									
								
								src/views/LandingPages/Project/MyProjects.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/views/LandingPages/Project/MyProjects.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | |||||||
|  | <script setup> | ||||||
|  | import { onMounted, onUnmounted, computed } from "vue"; | ||||||
|  | import axios from 'axios'; | ||||||
|  | import { ref } from "vue"; | ||||||
|  | import NavbarDefault from "../../../examples/navbars/NavbarDefault.vue"; | ||||||
|  | 
 | ||||||
|  | const searchQuery = ref(''); | ||||||
|  | const searchResultProjects = ref([]); | ||||||
|  | const searchResultUsers = ref([]); | ||||||
|  | 
 | ||||||
|  | const userId = computed(() => sessionStorage.getItem('user_id')); | ||||||
|  | const username = computed(() => sessionStorage.getItem('username')); | ||||||
|  | 
 | ||||||
|  | const search = async () => { | ||||||
|  |   try { | ||||||
|  |     const projectsResponse = await axios.get(`http://somebodyhire.me/api/search/projects/?search_query=${searchQuery.value}`); | ||||||
|  |     searchResultProjects.value = projectsResponse.data; | ||||||
|  | 
 | ||||||
|  |     const usersResponse = await axios.get(`http://somebodyhire.me/api/search/profiles/?search_query=${searchQuery.value}`); | ||||||
|  |     searchResultUsers.value = usersResponse.data; | ||||||
|  |   } catch (error) { | ||||||
|  |     console.error('There was an error fetching the search results', error); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | onMounted(() => { | ||||||
|  |   search(); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  | <NavbarDefault  /> | ||||||
|  |   <div> | ||||||
|  |     <h2 class="result-header">Проекты пользователя {{ username }}</h2> | ||||||
|  |     <div class="result-grid"> | ||||||
|  |       <div class="result-card" v-for="project in searchResultProjects" :key="project.id"> | ||||||
|  |         <div v-if = "project.owner == userId" class="project-owner-note"> | ||||||
|  |         <h3>{{ project.title }} with ID {{ project.id }}</h3> | ||||||
|  |         <p>{{ project.description }}</p> | ||||||
|  |         <p>Создатель: {{ project.owner }} </p> | ||||||
|  |         <a :href="`/project/${project.id}`">Страница проекта</a> | ||||||
|  |         <p></p> | ||||||
|  |         <a :href="`/editproject/${project.id}`">Редактирование проекта</a> | ||||||
|  | 
 | ||||||
|  |     </div>   | ||||||
|  |     </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |   </div> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   </template> | ||||||
|  | 
 | ||||||
|  | <style scoped> | ||||||
|  | .searchBar { | ||||||
|  |   display: flex; | ||||||
|  |   justify-content: center; | ||||||
|  |   align-items: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | .result-header { | ||||||
|  |   color: #fff; | ||||||
|  |   background-color: #333; | ||||||
|  |   padding: 10px; | ||||||
|  |   text-align: center; | ||||||
|  |   margin-top: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .result-grid { | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   justify-content: space-between; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .result-card { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   background-color: rgba(92, 90, 90, 0.5); | ||||||
|  |   padding: 10px; | ||||||
|  |   margin: 10px; | ||||||
|  |   border-radius: 10px; | ||||||
|  |   width: calc(100% / 3 - 20px); | ||||||
|  |   box-sizing: border-box; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media screen and (max-width: 992px) { | ||||||
|  |   .result-card { | ||||||
|  |     width: calc(100% / 2 - 20px); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media screen and (max-width: 600px) { | ||||||
|  |   .result-card { | ||||||
|  |     width: 100%; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | </style> | ||||||
| @ -1,13 +1,19 @@ | |||||||
| <script setup> | <script setup> | ||||||
| import axios from 'axios'; | import axios from 'axios'; | ||||||
| import { onMounted, ref } from "vue"; | import { onMounted, ref, computed } from "vue"; | ||||||
| import { useRoute } from "vue-router"; | import { useRoute } from "vue-router"; | ||||||
|  | import NavbarDefault from '../../../examples/navbars/NavbarDefault.vue'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| const projectId = ref(null); | const projectId = ref(null); | ||||||
| const route = useRoute(); | const route = useRoute(); | ||||||
| const projectData = ref([]); | const projectData = ref([]); | ||||||
| 
 | 
 | ||||||
|  | const isAuthenticated = computed(() => !!sessionStorage.getItem('access_token')); | ||||||
|  | const userId = computed(() => sessionStorage.getItem('user_id')); | ||||||
|  | const loggedUserName = computed(() => sessionStorage.getItem('username')); | ||||||
|  | const token = computed(() => sessionStorage.getItem('access_token')); | ||||||
|  | 
 | ||||||
| onMounted(async() => { | onMounted(async() => { | ||||||
|     projectId.value = route.params.id; |     projectId.value = route.params.id; | ||||||
|     await getProject(); |     await getProject(); | ||||||
| @ -30,27 +36,89 @@ const getProject = async () => { | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <div v-if="projectData"> |   <NavbarDefault /> | ||||||
|       <h1>Проект номер: {{ projectData.id }}</h1> |     <div v-if="projectData" class="project-container"> | ||||||
|       <h2>{{ projectData.title }}</h2> |       <h1 class="project-title">Проект номер: {{ projectData.id }}</h1> | ||||||
|       <p>{{ projectData.description }}</p> |       <h2 class="project-subtitle">{{ projectData.title }}</h2> | ||||||
|       <img :src="projectData.featured_image" alt="Featured image"> |       <div v-if = "projectData.owner == userId" class="project-owner-note"> | ||||||
|       <p v-if="projectData.demo_link">Demo Link: <a :href="projectData.demo_link">{{ projectData.demo_link }}</a></p> |         <a :href="`/editproject/${projectData.id}`">Редактирование проекта</a> | ||||||
|       <p v-if="projectData.source_link">Source Link: <a :href="projectData.source_link">{{ projectData.source_link }}</a></p> |       </div> | ||||||
|       <p>Total Votes: {{ projectData.vote_total }}</p> |       <p class="project-description">{{ projectData.description }}</p> | ||||||
|       <p>Vote Ratio: {{ projectData.vote_ratio }}</p> |       <img class="project-image" :src="projectData.featured_image" alt="Featured image"> | ||||||
|       <p>Created On: {{ new Date(projectData.created).toLocaleDateString() }}</p> |       <p v-if="projectData.demo_link" class="project-demo-link">Demo Link: <a :href="projectData.demo_link">{{ projectData.demo_link }}</a></p> | ||||||
|       <p>Owner ID: {{ projectData.owner }}</p> |       <p v-if="projectData.source_link" class="project-source-link">Source Link: <a :href="projectData.source_link">{{ projectData.source_link }}</a></p> | ||||||
|       <p>Tags:  |       <p class="project-votes">Total Votes: {{ projectData.vote_total }}</p> | ||||||
|         <span v-for="(tag, index) in projectData.tags" :key="index"> |       <p class="project-vote-ratio">Vote Ratio: {{ projectData.vote_ratio }}</p> | ||||||
|  |       <p class="project-created">Created On: {{ new Date(projectData.created).toLocaleDateString() }}</p> | ||||||
|  |       <p class="project-owner-id">Owner ID: {{ projectData.owner }}</p> | ||||||
|  |       <p class="project-tags">Tags:  | ||||||
|  |         <span v-for="(tag, index) in projectData.tags" :key="index" class="project-tag"> | ||||||
|           {{ tag }}<span v-if="index < projectData.tags.length - 1">, </span> |           {{ tag }}<span v-if="index < projectData.tags.length - 1">, </span> | ||||||
|         </span> |         </span> | ||||||
|       </p> |       </p> | ||||||
|     </div> |     </div> | ||||||
|   </template>  | </template>  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <style scoped> | <style scoped> | ||||||
|  | .project-container { | ||||||
|  |   margin: 20px; | ||||||
|  |   padding: 20px; | ||||||
|  |   border: 1px solid #ddd; | ||||||
|  |   border-radius: 5px; | ||||||
|  |   background-color: #f9f9f9; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-title { | ||||||
|  |   font-size: 24px; | ||||||
|  |   font-weight: bold; | ||||||
|  |   color: #333; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-subtitle { | ||||||
|  |   font-size: 18px; | ||||||
|  |   color: #555; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-owner-note { | ||||||
|  |   font-size: 16px; | ||||||
|  |   color: #777; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-description { | ||||||
|  |   font-size: 16px; | ||||||
|  |   color: #333; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-image { | ||||||
|  |   width: 100%; | ||||||
|  |   height: auto; | ||||||
|  |   margin-bottom: 20px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-demo-link, .project-source-link { | ||||||
|  |   font-size: 16px; | ||||||
|  |   color: #337ab7; | ||||||
|  |   text-decoration: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-votes, .project-vote-ratio, .project-created, .project-owner-id { | ||||||
|  |   font-size: 14px; | ||||||
|  |   color: #777; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-tags { | ||||||
|  |   font-size: 14px; | ||||||
|  |   color: #333; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .project-tag { | ||||||
|  |   background-color: #f0f0f0; | ||||||
|  |   border-radius: 5px; | ||||||
|  |   padding: 2px 5px; | ||||||
|  |   margin: 2px; | ||||||
|  | } | ||||||
| </style> | </style> | ||||||
|    |    | ||||||
| @ -15,9 +15,12 @@ const username = ref(''); | |||||||
| const password = ref(''); | const password = ref(''); | ||||||
| const errorMessage = ref(''); | const errorMessage = ref(''); | ||||||
| 
 | 
 | ||||||
| const isAuthenticated = computed(() => !!sessionStorage.getItem('access_token')); // Computed property to check if the user is authenticated | //Это блок для работы с хранилищем сессии | ||||||
|  | const isAuthenticated = computed(() => !!sessionStorage.getItem('access_token'));  | ||||||
| const userId = computed(() => sessionStorage.getItem('user_id')); | const userId = computed(() => sessionStorage.getItem('user_id')); | ||||||
| const loggedUserName = computed(() => sessionStorage.getItem('username')); | const loggedUserName = computed(() => sessionStorage.getItem('username')); | ||||||
|  | const isStaff = computed(() => sessionStorage.getItem('is_staff')); | ||||||
|  | const token = computed(() => sessionStorage.getItem('token')); | ||||||
| 
 | 
 | ||||||
| const login = async () => { | const login = async () => { | ||||||
|   if (!username.value || !password.value) { |   if (!username.value || !password.value) { | ||||||
| @ -34,10 +37,11 @@ const login = async () => { | |||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|       const response = await axios.post(url, body, { headers }); |       const response = await axios.post(url, body, { headers }); | ||||||
|       // Removed debug information from output |  | ||||||
|       sessionStorage.setItem('access_token', response.data.access);  |       sessionStorage.setItem('access_token', response.data.access);  | ||||||
|       sessionStorage.setItem('username', username.value); // Save username in sessionStorage |       sessionStorage.setItem('username', username.value);  | ||||||
|       sessionStorage.setItem('user_id', response.data.id); // Save the user id in sessionStorage |       sessionStorage.setItem('user_id', response.data.id);  | ||||||
|  |       sessionStorage.setItem('is_staff', response.data.is_staff);  | ||||||
|  |       sessionStorage.setItem('token', response.data.token); | ||||||
|       location.reload(); // Refresh page |       location.reload(); // Refresh page | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       if (error.response) { |       if (error.response) { | ||||||
| @ -55,6 +59,8 @@ const logout = () => { | |||||||
|   sessionStorage.removeItem('access_token'); |   sessionStorage.removeItem('access_token'); | ||||||
|   sessionStorage.removeItem('username'); // Also clear the username from sessionStorage |   sessionStorage.removeItem('username'); // Also clear the username from sessionStorage | ||||||
|   sessionStorage.removeItem('user_id'); |   sessionStorage.removeItem('user_id'); | ||||||
|  |   sessionStorage.setItem('is_staff', false); | ||||||
|  |   sessionStorage.removeItem('token'); | ||||||
|   location.reload(); // Refresh page after logout |   location.reload(); // Refresh page after logout | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -120,16 +126,22 @@ export default { | |||||||
|                   <div> |                   <div> | ||||||
|                     <div v-if="isAuthenticated"> |                     <div v-if="isAuthenticated"> | ||||||
|                         <!-- This will only be displayed if the user is authenticated --> |                         <!-- This will only be displayed if the user is authenticated --> | ||||||
|                         <p>Вы вошли в аккаунт {{ loggedUserName }}, ваш ID {{ userId }}</p> |                         <p>Вы вошли в аккаунт {{ loggedUserName }}</p> | ||||||
|  |                         <p> | ||||||
|  |                           <a href="/ViewMyProfile">Перейти в профиль.</a> | ||||||
|  |                         </p> | ||||||
|  |                         <!-- Это должно быть видно только админам --> | ||||||
|  |                         <div v-if="isStaff"> | ||||||
|  |                             <p> | ||||||
|  |                             <a href="/admin">Перейти в панель администратора.</a> | ||||||
|  |                             </p> | ||||||
|  |                           </div> | ||||||
|                         <button @click="logout">Выход</button> |                         <button @click="logout">Выход</button> | ||||||
|                     </div> |                     </div> | ||||||
| 
 | 
 | ||||||
|                     <div v-else> |                     <div v-else> | ||||||
|                         <!-- This will be displayed if the user is not authenticated --> |                         <!-- This will be displayed if the user is not authenticated --> | ||||||
|                         <p>Пожалуйста, введите логин и пароль</p> |                         <p>Пожалуйста, введите логин и пароль</p> | ||||||
|                      |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
|                       <div> |                       <div> | ||||||
|                         <input v-model="username" type="text" placeholder="Имя пользователя" /> |                         <input v-model="username" type="text" placeholder="Имя пользователя" /> | ||||||
| @ -150,6 +162,23 @@ export default { | |||||||
|                       </button> |                       </button> | ||||||
| 
 | 
 | ||||||
|                     </div> |                     </div> | ||||||
|  | 
 | ||||||
|  |                     <p class="mt-4 text-sm text-center"> | ||||||
|  |                     Нет аккаунта? | ||||||
|  |                     <a | ||||||
|  |                       href="/register" | ||||||
|  |                       class="text-success text-gradient font-weight-bold" | ||||||
|  |                       >Зарегистироваться</a | ||||||
|  |                     > | ||||||
|  |                   </p> | ||||||
|  |                   <p class="mt-4 text-sm text-center"> | ||||||
|  |                     | ||||||
|  |                    <a | ||||||
|  |                      href="/forgot" | ||||||
|  |                      class="text-success text-gradient font-weight-bold" | ||||||
|  |                      >Забыли пароль</a | ||||||
|  |                    > | ||||||
|  |                  </p> | ||||||
|                        |                        | ||||||
|                     </div> |                     </div> | ||||||
| 
 | 
 | ||||||
| @ -160,22 +189,8 @@ export default { | |||||||
|   </div> |   </div> | ||||||
|                    |                    | ||||||
| 
 | 
 | ||||||
|                   <p class="mt-4 text-sm text-center"> | 
 | ||||||
|                     Нет аккаунта? | 
 | ||||||
|                     <a |  | ||||||
|                       href="/register" |  | ||||||
|                       class="text-success text-gradient font-weight-bold" |  | ||||||
|                       >Зарегистироваться</a |  | ||||||
|                     > |  | ||||||
|                   </p> |  | ||||||
|                   <p class="mt-4 text-sm text-center"> |  | ||||||
|                     |  | ||||||
|                     <a |  | ||||||
|                       href="/forgot" |  | ||||||
|                       class="text-success text-gradient font-weight-bold" |  | ||||||
|                       >Забыли пароль</a |  | ||||||
|                     > |  | ||||||
|                   </p> |  | ||||||
|                 </form> |                 </form> | ||||||
|               </div> |               </div> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -1,18 +1,3 @@ | |||||||
| <style scoped> |  | ||||||
| .project-container { |  | ||||||
|   display: flex; |  | ||||||
|   flex-wrap: wrap; |  | ||||||
|   justify-content: space-between; |  | ||||||
| } |  | ||||||
| .project-card { |  | ||||||
|   flex-basis: calc(33.33% - 1em); /* 1em is for margin */ |  | ||||||
|   margin: 0.5em; |  | ||||||
|   box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); |  | ||||||
|   padding: 1em; |  | ||||||
|   box-sizing: border-box; |  | ||||||
| } |  | ||||||
| </style> |  | ||||||
| 
 |  | ||||||
| <script setup> | <script setup> | ||||||
| import { onMounted, onUnmounted, computed, } from "vue"; | import { onMounted, onUnmounted, computed, } from "vue"; | ||||||
| 
 | 
 | ||||||
| @ -124,4 +109,17 @@ export default { | |||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | <style scoped> | ||||||
|  | .project-container { | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   justify-content: space-between; | ||||||
|  | } | ||||||
|  | .project-card { | ||||||
|  |   flex-basis: calc(33.33% - 1em); /* 1em is for margin */ | ||||||
|  |   margin: 0.5em; | ||||||
|  |   box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); | ||||||
|  |   padding: 1em; | ||||||
|  |   box-sizing: border-box; | ||||||
|  | } | ||||||
|  | </style> | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 lazyseal
						lazyseal