Files
Modrinth-plus/apps/frontend/src/pages/auth/reset-password.vue
Calum H. 37eac92329 refactor: migrate all input fields to StyledInput (#5306)
* feat: StyledInput component

* migrate: auth pages to styledInput

* migrate: search/filter inputs

* migrate: dashboard inputs

* migrate: app frontend

* migrate: search related inputs

* migrate: all of app-frontend

* fix: missing inputs on app-frontend

* migrate: frontend

* feat: multiline

* migrate: textareas

* fix: storybook use text-primary

* fix: lint

* fix: merge conflict

* feat: cleanup
2026-02-09 14:57:31 +00:00

235 lines
5.9 KiB
Vue

<template>
<div>
<h1>{{ formatMessage(messages.longTitle) }}</h1>
<section class="auth-form">
<template v-if="step === 'choose_method'">
<p>
{{ formatMessage(methodChoiceMessages.description) }}
</p>
<label for="email" hidden>
{{ formatMessage(commonMessages.emailUsernameLabel) }}
</label>
<StyledInput
id="email"
v-model="email"
:icon="MailIcon"
type="text"
autocomplete="username"
:placeholder="formatMessage(commonMessages.emailLabel)"
wrapper-class="w-full"
/>
<HCaptcha v-if="globals?.captcha_enabled" ref="captcha" v-model="token" />
<button
class="btn btn-primary centered-btn"
:disabled="globals?.captcha_enabled ? !token : false"
@click="recovery"
>
<SendIcon /> {{ formatMessage(methodChoiceMessages.action) }}
</button>
</template>
<template v-else-if="step === 'passed_challenge'">
<p>{{ formatMessage(postChallengeMessages.description) }}</p>
<label for="password" hidden>{{ formatMessage(commonMessages.passwordLabel) }}</label>
<StyledInput
id="password"
v-model="newPassword"
:icon="KeyIcon"
type="password"
autocomplete="new-password"
:placeholder="formatMessage(commonMessages.passwordLabel)"
wrapper-class="w-full"
/>
<label for="confirm-password" hidden>
{{ formatMessage(commonMessages.passwordLabel) }}
</label>
<StyledInput
id="confirm-password"
v-model="confirmNewPassword"
:icon="KeyIcon"
type="password"
autocomplete="new-password"
:placeholder="formatMessage(postChallengeMessages.confirmPasswordLabel)"
wrapper-class="w-full"
/>
<button class="auth-form__input btn btn-primary continue-btn" @click="changePassword">
{{ formatMessage(postChallengeMessages.action) }}
</button>
</template>
</section>
</div>
</template>
<script setup>
import { KeyIcon, MailIcon, SendIcon } from '@modrinth/assets'
import {
commonMessages,
defineMessages,
injectNotificationManager,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import HCaptcha from '@/components/ui/HCaptcha.vue'
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
const methodChoiceMessages = defineMessages({
description: {
id: 'auth.reset-password.method-choice.description',
defaultMessage:
"Enter your email below and we'll send a recovery link to allow you to recover your account.",
},
action: {
id: 'auth.reset-password.method-choice.action',
defaultMessage: 'Send recovery email',
},
})
const postChallengeMessages = defineMessages({
description: {
id: 'auth.reset-password.post-challenge.description',
defaultMessage: 'Enter your new password below to gain access to your account.',
},
confirmPasswordLabel: {
id: 'auth.reset-password.post-challenge.confirm-password.label',
defaultMessage: 'Confirm password',
},
action: {
id: 'auth.reset-password.post-challenge.action',
defaultMessage: 'Reset password',
},
})
// NOTE(Brawaru): Vite uses esbuild for minification so can't combine these
// because it'll keep the original prop names compared to consts, which names
// will be mangled.
const emailSentNotificationMessages = defineMessages({
title: {
id: 'auth.reset-password.notification.email-sent.title',
defaultMessage: 'Email sent',
},
text: {
id: 'auth.reset-password.notification.email-sent.text',
defaultMessage:
'An email with instructions has been sent to you if the email was previously saved on your account.',
},
})
const passwordResetNotificationMessages = defineMessages({
title: {
id: 'auth.reset-password.notification.password-reset.title',
defaultMessage: 'Password successfully reset',
},
text: {
id: 'auth.reset-password.notification.password-reset.text',
defaultMessage: 'You can now log-in into your account with your new password.',
},
})
const messages = defineMessages({
title: {
id: 'auth.reset-password.title',
defaultMessage: 'Reset Password',
},
longTitle: {
id: 'auth.reset-password.title.long',
defaultMessage: 'Reset your password',
},
})
useHead({
title: () => `${formatMessage(messages.title)} - Modrinth`,
})
const auth = await useAuth()
if (auth.value.user) {
await navigateTo('/dashboard')
}
const route = useNativeRoute()
const step = ref('choose_method')
if (route.query.flow) {
step.value = 'passed_challenge'
}
const captcha = ref()
const { data: globals } = await useAsyncData('auth-globals', async () => {
try {
return await useBaseFetch('globals', { internal: true })
} catch (err) {
console.error('Error fetching globals:', err)
return { captcha_enabled: true }
}
})
const email = ref('')
const token = ref('')
async function recovery() {
startLoading()
try {
await useBaseFetch('auth/password/reset', {
method: 'POST',
body: {
username: email.value,
challenge: token.value,
},
})
addNotification({
title: formatMessage(emailSentNotificationMessages.title),
text: formatMessage(emailSentNotificationMessages.text),
type: 'success',
})
} catch (err) {
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: 'error',
})
captcha.value?.reset()
}
stopLoading()
}
const newPassword = ref('')
const confirmNewPassword = ref('')
async function changePassword() {
startLoading()
try {
await useBaseFetch('auth/password', {
method: 'PATCH',
body: {
new_password: newPassword.value,
flow: route.query.flow,
},
})
addNotification({
title: formatMessage(passwordResetNotificationMessages.title),
text: formatMessage(passwordResetNotificationMessages.text),
type: 'success',
})
await navigateTo('/auth/sign-in')
} catch (err) {
addNotification({
title: formatMessage(commonMessages.errorNotificationTitle),
text: err.data ? err.data.description : err,
type: 'error',
})
captcha.value?.reset()
}
stopLoading()
}
</script>