Changeset ae83647 for petify-frontend
- Timestamp:
- 06/26/26 16:32:12 (2 days ago)
- Branches:
- master
- Parents:
- fa32d0f
- Location:
- petify-frontend/src
- Files:
-
- 3 edited
-
api/auth.ts (modified) (2 diffs)
-
views/LoginView.vue (modified) (9 diffs)
-
views/ProfileView.vue (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
petify-frontend/src/api/auth.ts
rfa32d0f rae83647 24 24 } 25 25 message?: string 26 } 27 28 export interface ChangePasswordRequest { 29 userId: number 30 currentPassword: string 31 newPassword: string 32 } 33 34 export interface ForgotPasswordRequest { 35 identifier: string 26 36 } 27 37 … … 141 151 return { message: data.message || "Registration successful" } 142 152 } 153 154 export async function changePassword(payload: ChangePasswordRequest, options?: { signal?: AbortSignal }): Promise<void> { 155 await postJson<{ message?: string }>('/api/auth/change-password', payload, options) 156 } 157 158 export async function forgotPassword(payload: ForgotPasswordRequest, options?: { signal?: AbortSignal }): Promise<string> { 159 const data = await postJson<{ message?: string }>('/api/auth/forgot-password', payload, options) 160 return data.message || 'If an account matches that username or email, a temporary password has been sent.' 161 } -
petify-frontend/src/views/LoginView.vue
rfa32d0f rae83647 29 29 <div v-if="error" class="alert alert-danger auth-alert" role="alert"> 30 30 <strong class="me-1">Oops.</strong>{{ error }} 31 </div> 32 <div v-if="forgotSuccess" class="alert alert-success auth-alert" role="alert"> 33 {{ forgotSuccess }} 31 34 </div> 32 35 … … 113 116 <!-- Remember me --> 114 117 <div class="d-flex justify-content-between align-items-center mt-2 mb-3"> 115 116 117 <!-- enable later if you implement route -->118 < !-- <RouterLink class="link small accent" to="/forgot">Forgot password?</RouterLink> -->118 <span></span> 119 <button class="link-button small accent" type="button" @click="toggleForgotPassword"> 120 Forgot password? 121 </button> 119 122 </div> 120 123 … … 123 126 <span v-if="loading" class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span> 124 127 {{ loading ? 'Logging in…' : 'Log in' }} 128 </button> 129 </form> 130 131 <form v-if="showForgotPassword" class="forgot-panel mt-4" @submit.prevent="submitForgotPassword"> 132 <label class="form-label" for="forgot-identifier">Username or email</label> 133 <div class="input-group auth-input"> 134 <span class="input-group-text"> 135 <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> 136 <path 137 d="M4 6.5A2.5 2.5 0 0 1 6.5 4h11A2.5 2.5 0 0 1 20 6.5v11a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 4 17.5v-11Zm2.5-.5a.5.5 0 0 0-.5.5v.8l6 3.7 6-3.7v-.8a.5.5 0 0 0-.5-.5h-11Zm11.5 3.6-5.5 3.4a1 1 0 0 1-1 0L6 9.6v7.9a.5.5 0 0 0 .5.5h11a.5.5 0 0 0 .5-.5V9.6Z" 138 fill="currentColor" 139 opacity=".85" 140 /> 141 </svg> 142 </span> 143 <input 144 id="forgot-identifier" 145 v-model.trim="forgotIdentifier" 146 class="form-control" 147 type="text" 148 autocomplete="username" 149 required 150 placeholder="username or email" 151 /> 152 </div> 153 <div v-if="forgotError" class="text-danger small mt-2">{{ forgotError }}</div> 154 <button class="btn btn-outline-primary w-100 mt-3" type="submit" :disabled="forgotLoading"> 155 <span v-if="forgotLoading" class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span> 156 {{ forgotLoading ? 'Sending...' : 'Send temporary password' }} 125 157 </button> 126 158 </form> … … 144 176 import { useRoute, useRouter } from 'vue-router' 145 177 import { useAuthStore } from '../stores/auth' 178 import { forgotPassword } from '../api/auth' 146 179 147 180 const auth = useAuthStore() … … 153 186 const loading = ref(false) 154 187 const error = ref<string | null>(null) 188 const forgotLoading = ref(false) 189 const forgotError = ref<string | null>(null) 190 const forgotSuccess = ref<string | null>(null) 191 const showForgotPassword = ref(false) 192 const forgotIdentifier = ref('') 155 193 156 194 const showPassword = ref(false) … … 159 197 function togglePassword() { 160 198 showPassword.value = !showPassword.value 199 } 200 201 function toggleForgotPassword() { 202 showForgotPassword.value = !showForgotPassword.value 203 forgotError.value = null 204 forgotSuccess.value = null 205 if (showForgotPassword.value && !forgotIdentifier.value) { 206 forgotIdentifier.value = username.value 207 } 161 208 } 162 209 … … 175 222 } finally { 176 223 loading.value = false 224 } 225 } 226 227 async function submitForgotPassword() { 228 forgotError.value = null 229 forgotSuccess.value = null 230 forgotLoading.value = true 231 try { 232 forgotSuccess.value = await forgotPassword({ identifier: forgotIdentifier.value }) 233 showForgotPassword.value = false 234 } catch (e) { 235 forgotError.value = e instanceof Error ? e.message : String(e) 236 } finally { 237 forgotLoading.value = false 177 238 } 178 239 } … … 276 337 } 277 338 339 .forgot-panel { 340 border-top: 1px solid rgba(31, 41, 55, 0.1); 341 padding-top: 1rem; 342 } 343 278 344 /* Input styling */ 279 345 .auth-input .input-group-text { … … 371 437 } 372 438 439 .link-button { 440 border: 0; 441 background: transparent; 442 padding: 0; 443 text-decoration: none; 444 } 445 446 .link-button.accent { 447 color: #ff7a18; 448 font-weight: 600; 449 } 450 451 .link-button.accent:hover { 452 color: #e76610; 453 } 454 373 455 /* Respect reduced motion */ 374 456 @media (prefers-reduced-motion: reduce) { -
petify-frontend/src/views/ProfileView.vue
rfa32d0f rae83647 94 94 </button> 95 95 </li> 96 <li class="nav-item" role="presentation"> 97 <button 98 class="nav-link" 99 :class="{ active: activeTab === 'account' }" 100 @click="activeTab = 'account'" 101 type="button" 102 role="tab" 103 > 104 <i class="bi bi-shield-lock-fill"></i> Account 105 </button> 106 </li> 96 107 </ul> 108 109 <!-- Account Tab --> 110 <div v-if="activeTab === 'account'" class="tab-content-section"> 111 <h2 class="section-title">Account</h2> 112 <form class="password-form" @submit.prevent="submitPasswordChange"> 113 <div v-if="passwordSuccess" class="alert alert-success" role="alert"> 114 {{ passwordSuccess }} 115 </div> 116 <div v-if="passwordError" class="alert alert-danger" role="alert"> 117 {{ passwordError }} 118 </div> 119 120 <div class="mb-3"> 121 <label class="form-label" for="current-password">Current password</label> 122 <input 123 id="current-password" 124 v-model="passwordForm.currentPassword" 125 class="form-control" 126 type="password" 127 autocomplete="current-password" 128 required 129 /> 130 </div> 131 132 <div class="mb-3"> 133 <label class="form-label" for="new-password">New password</label> 134 <input 135 id="new-password" 136 v-model="passwordForm.newPassword" 137 class="form-control" 138 type="password" 139 autocomplete="new-password" 140 minlength="8" 141 required 142 /> 143 </div> 144 145 <div class="mb-4"> 146 <label class="form-label" for="confirm-new-password">Confirm new password</label> 147 <input 148 id="confirm-new-password" 149 v-model="passwordForm.confirmPassword" 150 class="form-control" 151 type="password" 152 autocomplete="new-password" 153 minlength="8" 154 required 155 /> 156 </div> 157 158 <button class="btn btn-primary" type="submit" :disabled="isPasswordSubmitting"> 159 <span v-if="isPasswordSubmitting" class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span> 160 {{ isPasswordSubmitting ? 'Changing...' : 'Change password' }} 161 </button> 162 </form> 163 </div> 97 164 98 165 <!-- Listings Tab --> … … 746 813 type Review, 747 814 } from '../api/reviews' 815 import { changePassword } from '../api/auth' 748 816 749 817 const router = useRouter() 750 818 const auth = useAuthStore() 751 819 752 const activeTab = ref<'listings' | 'pets' | 'create-listing' | 'favorites' | 'appointments' >('listings')820 const activeTab = ref<'listings' | 'pets' | 'create-listing' | 'favorites' | 'appointments' | 'account'>('listings') 753 821 const listings = ref<any[]>([]) 754 822 const pets = ref<any[]>([]) … … 782 850 const petPhotoFile = ref<File | null>(null) 783 851 const petPhotoPreview = ref('') 852 const isPasswordSubmitting = ref(false) 853 const passwordError = ref('') 854 const passwordSuccess = ref('') 855 856 const passwordForm = ref({ 857 currentPassword: '', 858 newPassword: '', 859 confirmPassword: '', 860 }) 784 861 785 862 const newListing = ref({ … … 859 936 // Fall back to petNameMap (from pets list) 860 937 return petNameMap.value[animalId] || 'Unknown Pet' 938 } 939 940 async function submitPasswordChange() { 941 passwordError.value = '' 942 passwordSuccess.value = '' 943 944 if (!auth.user?.userId) { 945 passwordError.value = 'You need to be logged in to change your password.' 946 return 947 } 948 if (passwordForm.value.newPassword.length < 8) { 949 passwordError.value = 'New password must be at least 8 characters long.' 950 return 951 } 952 if (passwordForm.value.newPassword !== passwordForm.value.confirmPassword) { 953 passwordError.value = 'New passwords do not match.' 954 return 955 } 956 957 isPasswordSubmitting.value = true 958 try { 959 await changePassword({ 960 userId: auth.user.userId, 961 currentPassword: passwordForm.value.currentPassword, 962 newPassword: passwordForm.value.newPassword, 963 }) 964 passwordForm.value = { 965 currentPassword: '', 966 newPassword: '', 967 confirmPassword: '', 968 } 969 passwordSuccess.value = 'Password changed successfully.' 970 } catch (error: any) { 971 passwordError.value = error?.message || 'Failed to change password.' 972 } finally { 973 isPasswordSubmitting.value = false 974 } 861 975 } 862 976 … … 1670 1784 } 1671 1785 1786 .password-form { 1787 max-width: 520px; 1788 } 1789 1672 1790 @keyframes fadeIn { 1673 1791 from {
Note:
See TracChangeset
for help on using the changeset viewer.
