Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add password reset page #170

Merged
merged 3 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
Loading