Changeset ae83647 for petify-frontend


Ignore:
Timestamp:
06/26/26 16:32:12 (2 days ago)
Author:
veronika-ils <ilioskaveronika@…>
Branches:
master
Parents:
fa32d0f
Message:

add functionality so that users can change passwords

Location:
petify-frontend/src
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • petify-frontend/src/api/auth.ts

    rfa32d0f rae83647  
    2424  }
    2525  message?: string
     26}
     27
     28export interface ChangePasswordRequest {
     29  userId: number
     30  currentPassword: string
     31  newPassword: string
     32}
     33
     34export interface ForgotPasswordRequest {
     35  identifier: string
    2636}
    2737
     
    141151  return { message: data.message || "Registration successful" }
    142152}
     153
     154export async function changePassword(payload: ChangePasswordRequest, options?: { signal?: AbortSignal }): Promise<void> {
     155  await postJson<{ message?: string }>('/api/auth/change-password', payload, options)
     156}
     157
     158export 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  
    2929        <div v-if="error" class="alert alert-danger auth-alert" role="alert">
    3030          <strong class="me-1">Oops.</strong>{{ error }}
     31        </div>
     32        <div v-if="forgotSuccess" class="alert alert-success auth-alert" role="alert">
     33          {{ forgotSuccess }}
    3134        </div>
    3235
     
    113116          <!-- Remember me -->
    114117          <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>
    119122          </div>
    120123
     
    123126            <span v-if="loading" class="spinner-border spinner-border-sm me-2" aria-hidden="true"></span>
    124127            {{ 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' }}
    125157          </button>
    126158        </form>
     
    144176import { useRoute, useRouter } from 'vue-router'
    145177import { useAuthStore } from '../stores/auth'
     178import { forgotPassword } from '../api/auth'
    146179
    147180const auth = useAuthStore()
     
    153186const loading = ref(false)
    154187const error = ref<string | null>(null)
     188const forgotLoading = ref(false)
     189const forgotError = ref<string | null>(null)
     190const forgotSuccess = ref<string | null>(null)
     191const showForgotPassword = ref(false)
     192const forgotIdentifier = ref('')
    155193
    156194const showPassword = ref(false)
     
    159197function togglePassword() {
    160198  showPassword.value = !showPassword.value
     199}
     200
     201function 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  }
    161208}
    162209
     
    175222  } finally {
    176223    loading.value = false
     224  }
     225}
     226
     227async 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
    177238  }
    178239}
     
    276337}
    277338
     339.forgot-panel {
     340  border-top: 1px solid rgba(31, 41, 55, 0.1);
     341  padding-top: 1rem;
     342}
     343
    278344/* Input styling */
    279345.auth-input .input-group-text {
     
    371437}
    372438
     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
    373455/* Respect reduced motion */
    374456@media (prefers-reduced-motion: reduce) {
  • petify-frontend/src/views/ProfileView.vue

    rfa32d0f rae83647  
    9494              </button>
    9595            </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>
    96107          </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>
    97164
    98165          <!-- Listings Tab -->
     
    746813  type Review,
    747814} from '../api/reviews'
     815import { changePassword } from '../api/auth'
    748816
    749817const router = useRouter()
    750818const auth = useAuthStore()
    751819
    752 const activeTab = ref<'listings' | 'pets' | 'create-listing' | 'favorites' | 'appointments'>('listings')
     820const activeTab = ref<'listings' | 'pets' | 'create-listing' | 'favorites' | 'appointments' | 'account'>('listings')
    753821const listings = ref<any[]>([])
    754822const pets = ref<any[]>([])
     
    782850const petPhotoFile = ref<File | null>(null)
    783851const petPhotoPreview = ref('')
     852const isPasswordSubmitting = ref(false)
     853const passwordError = ref('')
     854const passwordSuccess = ref('')
     855
     856const passwordForm = ref({
     857  currentPassword: '',
     858  newPassword: '',
     859  confirmPassword: '',
     860})
    784861
    785862const newListing = ref({
     
    859936  // Fall back to petNameMap (from pets list)
    860937  return petNameMap.value[animalId] || 'Unknown Pet'
     938}
     939
     940async 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  }
    861975}
    862976
     
    16701784}
    16711785
     1786.password-form {
     1787  max-width: 520px;
     1788}
     1789
    16721790@keyframes fadeIn {
    16731791  from {
Note: See TracChangeset for help on using the changeset viewer.