Skip to content

Commit

Permalink
Merge pull request #170 from Normal-OJ/feat/add-password-reset-page
Browse files Browse the repository at this point in the history
feat: add password reset page
  • Loading branch information
Bogay authored Oct 10, 2024
2 parents 9b1ac1d + 5a101c0 commit 6b41066
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/components/LoginSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async function login() {
@keydown.enter="login"
/>
<label class="label flex-row-reverse">
<a href="#" class="link-hover label-text-alt link">{{
<a href="/password_reset" class="link-hover label-text-alt link">{{
$t("components.loginSection.forgot")
}}</a>
<span
Expand Down
11 changes: 11 additions & 0 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -402,5 +402,16 @@
"ERR001": "Login Failed: Your username/email or password is incorrect.",
"ERR002": "Login Failed: Your account is not activated yet, please go to https://v1.noj.tw to verify your email.",
"UNKNOWN": "Unknown Error, please contact teaching assistants or NOJ maintainers, or try again later."
},
"password_reset": {
"forgot-password": "Forgot password",
"description": "Please enter email address which you registered with, we'll send email to recovery your password.",
"submit": "Submit",
"email": "Email address",
"status": {
"error": "Failed to send an email to you, we cannot find this email.",
"success": "Email sent, please check your inbox."
},
"return-home": "Return Home"
}
}
11 changes: 11 additions & 0 deletions src/i18n/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -401,5 +401,16 @@
"ERR001": "登入失敗:帳號或密碼錯誤。",
"ERR002": "登入失敗:帳號尚未開通,請至 https://v1.noj.tw 驗證信箱以開通帳號。",
"UNKNOWN": "未知的錯誤,請洽助教或管理者協助處理,或請稍後再試一次。"
},
"password_reset": {
"forgot-password": "忘記密碼",
"description": "請輸入你註冊用的信箱,我們會傳送一封電子郵件給你。",
"submit": "送出",
"email": "電子郵件",
"status": {
"error": "找不到該信箱。",
"success": "發送成功,請檢查你的電子信箱。"
},
"return-home": "回到首頁"
}
}
2 changes: 2 additions & 0 deletions src/models/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const Auth = {
fetcher.post("/auth/change-password", body),
batchSignup: (body: { newUsers: string; force: boolean; course: string }) =>
fetcher.post("/auth/batch-signup", body),
checkEmail: (body: { email: string }) => fetcher.post<CheckEmail>("/auth/check/email", body),
sendRecoveryEmail: (body: { email: string }) => fetcher.post("/auth/password-recovery", body),
};

const Problem = {
Expand Down
88 changes: 88 additions & 0 deletions src/pages/password_reset.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<script setup lang="ts">
import { reactive, ref } from "vue";
import { useTitle } from "@vueuse/core";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
import useVuelidate from "@vuelidate/core";
import { email } from "@vuelidate/validators";
import api from "@/models/api";
const router = useRouter();
const rules = {
email: { email },
};
const form = reactive({
email: "",
});
const v$ = useVuelidate(rules, form);
const { t } = useI18n();
const showError = ref(false);
const success = ref(false);
const handleSubmit = async () => {
if (!v$.value.$validate()) return;
const checkEmail = await api.Auth.checkEmail({ email: form.email });
if (checkEmail.data.valid === 1) {
showError.value = true;
return;
}
const res = await api.Auth.sendRecoveryEmail({ email: form.email });
if (res.statusText === "OK") {
success.value = true;
}
};
useTitle("Forgot Password");
</script>

<template>
<div class="mx-4 flex max-w-4xl flex-col items-center justify-center gap-4 p-4 md:mx-auto">
<h1 class="my-12 text-center text-4xl font-bold">{{ t("password_reset.forgot-password") }}</h1>
<div class="card w-96 max-w-full bg-base-200 shadow-xl">
<div v-if="!success" class="card-body">
<div class="card-title flex-col">
<div v-if="showError" class="alert alert-error text-base">
{{ t("password_reset.status.error") }}
<div class="flex-none">
<button @click="showError = false" class="btn btn-ghost btn-sm btn-circle">X</button>
</div>
</div>
<span class="text-base font-semibold">
{{ t("password_reset.description") }}
</span>
</div>
<div class="form-control">
<input
v-model="v$.email.$model"
type="email"
name="Email"
:placeholder="$t('password_reset.email')"
:class="['input-bordered input', v$.email.$error && 'input-error']"
/>
<label class="label" v-show="v$.email.$error">
<span class="label-text-alt text-error" v-text="v$.email.$errors[0]?.$message" />
</label>
</div>
<div class="card-actions justify-center">
<button class="btn-primary btn" @click="() => handleSubmit()">
{{ t("password_reset.submit") }}
</button>
</div>
</div>
<div v-else class="card-body">
{{ t("password_reset.status.success") }}
<div class="card-actions justify-center">
<button class="btn-primary btn" @click="() => router.push('/')">
{{ t("password_reset.return-home") }}
</button>
</div>
</div>
</div>
</div>
</template>
8 changes: 7 additions & 1 deletion src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ const router = createRouter({
routes,
});

const publicPages = [/^\/$/, /^\/about$/, /^\/announcements\/[0-9A-Fa-f]+$/, /^\/settings$/];
const publicPages = [
/^\/$/,
/^\/about$/,
/^\/announcements\/[0-9A-Fa-f]+$/,
/^\/settings$/,
/^\/password_reset$/,
];

router.beforeEach(async (to) => {
const session = useSession();
Expand Down
4 changes: 4 additions & 0 deletions src/types/auth.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ interface UserEditionForm {
displayedName: string;
role: number;
}

interface CheckEmail {
valid: number; // 1 for valid/unused email
}

0 comments on commit 6b41066

Please sign in to comment.