Skip to content

Commit 6b41066

Browse files
authored
Merge pull request #170 from Normal-OJ/feat/add-password-reset-page
feat: add password reset page
2 parents 9b1ac1d + 5a101c0 commit 6b41066

File tree

7 files changed

+124
-2
lines changed

7 files changed

+124
-2
lines changed

src/components/LoginSection.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ async function login() {
115115
@keydown.enter="login"
116116
/>
117117
<label class="label flex-row-reverse">
118-
<a href="#" class="link-hover label-text-alt link">{{
118+
<a href="/password_reset" class="link-hover label-text-alt link">{{
119119
$t("components.loginSection.forgot")
120120
}}</a>
121121
<span

src/i18n/en.json

+11
Original file line numberDiff line numberDiff line change
@@ -402,5 +402,16 @@
402402
"ERR001": "Login Failed: Your username/email or password is incorrect.",
403403
"ERR002": "Login Failed: Your account is not activated yet, please go to https://v1.noj.tw to verify your email.",
404404
"UNKNOWN": "Unknown Error, please contact teaching assistants or NOJ maintainers, or try again later."
405+
},
406+
"password_reset": {
407+
"forgot-password": "Forgot password",
408+
"description": "Please enter email address which you registered with, we'll send email to recovery your password.",
409+
"submit": "Submit",
410+
"email": "Email address",
411+
"status": {
412+
"error": "Failed to send an email to you, we cannot find this email.",
413+
"success": "Email sent, please check your inbox."
414+
},
415+
"return-home": "Return Home"
405416
}
406417
}

src/i18n/zh-tw.json

+11
Original file line numberDiff line numberDiff line change
@@ -401,5 +401,16 @@
401401
"ERR001": "登入失敗:帳號或密碼錯誤。",
402402
"ERR002": "登入失敗:帳號尚未開通,請至 https://v1.noj.tw 驗證信箱以開通帳號。",
403403
"UNKNOWN": "未知的錯誤,請洽助教或管理者協助處理,或請稍後再試一次。"
404+
},
405+
"password_reset": {
406+
"forgot-password": "忘記密碼",
407+
"description": "請輸入你註冊用的信箱,我們會傳送一封電子郵件給你。",
408+
"submit": "送出",
409+
"email": "電子郵件",
410+
"status": {
411+
"error": "找不到該信箱。",
412+
"success": "發送成功,請檢查你的電子信箱。"
413+
},
414+
"return-home": "回到首頁"
404415
}
405416
}

src/models/api.ts

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const Auth = {
3030
fetcher.post("/auth/change-password", body),
3131
batchSignup: (body: { newUsers: string; force: boolean; course: string }) =>
3232
fetcher.post("/auth/batch-signup", body),
33+
checkEmail: (body: { email: string }) => fetcher.post<CheckEmail>("/auth/check/email", body),
34+
sendRecoveryEmail: (body: { email: string }) => fetcher.post("/auth/password-recovery", body),
3335
};
3436

3537
const Problem = {

src/pages/password_reset.vue

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<script setup lang="ts">
2+
import { reactive, ref } from "vue";
3+
import { useTitle } from "@vueuse/core";
4+
import { useI18n } from "vue-i18n";
5+
import { useRouter } from "vue-router";
6+
import useVuelidate from "@vuelidate/core";
7+
import { email } from "@vuelidate/validators";
8+
import api from "@/models/api";
9+
10+
const router = useRouter();
11+
12+
const rules = {
13+
email: { email },
14+
};
15+
16+
const form = reactive({
17+
email: "",
18+
});
19+
const v$ = useVuelidate(rules, form);
20+
21+
const { t } = useI18n();
22+
23+
const showError = ref(false);
24+
const success = ref(false);
25+
26+
const handleSubmit = async () => {
27+
if (!v$.value.$validate()) return;
28+
29+
const checkEmail = await api.Auth.checkEmail({ email: form.email });
30+
if (checkEmail.data.valid === 1) {
31+
showError.value = true;
32+
return;
33+
}
34+
35+
const res = await api.Auth.sendRecoveryEmail({ email: form.email });
36+
if (res.statusText === "OK") {
37+
success.value = true;
38+
}
39+
};
40+
41+
useTitle("Forgot Password");
42+
</script>
43+
44+
<template>
45+
<div class="mx-4 flex max-w-4xl flex-col items-center justify-center gap-4 p-4 md:mx-auto">
46+
<h1 class="my-12 text-center text-4xl font-bold">{{ t("password_reset.forgot-password") }}</h1>
47+
<div class="card w-96 max-w-full bg-base-200 shadow-xl">
48+
<div v-if="!success" class="card-body">
49+
<div class="card-title flex-col">
50+
<div v-if="showError" class="alert alert-error text-base">
51+
{{ t("password_reset.status.error") }}
52+
<div class="flex-none">
53+
<button @click="showError = false" class="btn btn-ghost btn-sm btn-circle">X</button>
54+
</div>
55+
</div>
56+
<span class="text-base font-semibold">
57+
{{ t("password_reset.description") }}
58+
</span>
59+
</div>
60+
<div class="form-control">
61+
<input
62+
v-model="v$.email.$model"
63+
type="email"
64+
name="Email"
65+
:placeholder="$t('password_reset.email')"
66+
:class="['input-bordered input', v$.email.$error && 'input-error']"
67+
/>
68+
<label class="label" v-show="v$.email.$error">
69+
<span class="label-text-alt text-error" v-text="v$.email.$errors[0]?.$message" />
70+
</label>
71+
</div>
72+
<div class="card-actions justify-center">
73+
<button class="btn-primary btn" @click="() => handleSubmit()">
74+
{{ t("password_reset.submit") }}
75+
</button>
76+
</div>
77+
</div>
78+
<div v-else class="card-body">
79+
{{ t("password_reset.status.success") }}
80+
<div class="card-actions justify-center">
81+
<button class="btn-primary btn" @click="() => router.push('/')">
82+
{{ t("password_reset.return-home") }}
83+
</button>
84+
</div>
85+
</div>
86+
</div>
87+
</div>
88+
</template>

src/router.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ const router = createRouter({
77
routes,
88
});
99

10-
const publicPages = [/^\/$/, /^\/about$/, /^\/announcements\/[0-9A-Fa-f]+$/, /^\/settings$/];
10+
const publicPages = [
11+
/^\/$/,
12+
/^\/about$/,
13+
/^\/announcements\/[0-9A-Fa-f]+$/,
14+
/^\/settings$/,
15+
/^\/password_reset$/,
16+
];
1117

1218
router.beforeEach(async (to) => {
1319
const session = useSession();

src/types/auth.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ interface UserEditionForm {
2727
displayedName: string;
2828
role: number;
2929
}
30+
31+
interface CheckEmail {
32+
valid: number; // 1 for valid/unused email
33+
}

0 commit comments

Comments
 (0)