ADD:API RESET -request and password and Refresh access token

This commit is contained in:
supalerk-ar66 2026-01-15 11:06:44 +07:00
parent c557c383e3
commit ae771420be
8 changed files with 55 additions and 29 deletions

View file

@ -1 +1 @@
{"id":"dev","timestamp":1768447757574} {"id":"dev","timestamp":1768449730995}

View file

@ -1 +1 @@
{"id":"dev","timestamp":1768447757574,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]} {"id":"dev","timestamp":1768449730995,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}

View file

@ -1,5 +1,5 @@
{ {
"date": "2026-01-15T03:29:24.697Z", "date": "2026-01-15T04:02:21.792Z",
"preset": "nitro-dev", "preset": "nitro-dev",
"framework": { "framework": {
"name": "nuxt", "name": "nuxt",
@ -9,9 +9,9 @@
"nitro": "2.12.8" "nitro": "2.12.8"
}, },
"dev": { "dev": {
"pid": 15120, "pid": 1972,
"workerAddress": { "workerAddress": {
"socketPath": "\\\\.\\pipe\\nitro-worker-15120-1-1-7719.sock" "socketPath": "\\\\.\\pipe\\nitro-worker-1972-1-1-5250.sock"
} }
} }
} }

View file

@ -1,7 +1,7 @@
/// <reference types="quasar" /> /// <reference types="quasar" />
/// <reference types="@nuxtjs/tailwindcss" />
/// <reference types="nuxt-quasar-ui" /> /// <reference types="nuxt-quasar-ui" />
/// <reference types="@nuxt/devtools" /> /// <reference types="@nuxt/devtools" />
/// <reference types="@nuxtjs/tailwindcss" />
/// <reference types="@nuxt/telemetry" /> /// <reference types="@nuxt/telemetry" />
/// <reference path="types/builder-env.d.ts" /> /// <reference path="types/builder-env.d.ts" />
/// <reference types="nuxt" /> /// <reference types="nuxt" />

View file

@ -1,4 +1,4 @@
// generated by the @nuxtjs/tailwindcss <https://github.com/nuxt-modules/tailwindcss> module at 15/1/2569 10:29:18 // generated by the @nuxtjs/tailwindcss <https://github.com/nuxt-modules/tailwindcss> module at 15/1/2569 11:02:11
import "@nuxtjs/tailwindcss/config-ctx" import "@nuxtjs/tailwindcss/config-ctx"
import configMerger from "@nuxtjs/tailwindcss/merger"; import configMerger from "@nuxtjs/tailwindcss/merger";

View file

@ -183,7 +183,7 @@ export const useAuth = () => {
} }
// Confirm Reset Password // Confirm Reset Password
const confirmResetPassword = async (payload: { id: number; token: string; password: string }) => { const confirmResetPassword = async (payload: { token: string; password: string }) => {
try { try {
const { error } = await useFetch(`${API_BASE_URL}/auth/reset-password`, { const { error } = await useFetch(`${API_BASE_URL}/auth/reset-password`, {
method: 'POST', method: 'POST',

View file

@ -24,7 +24,25 @@ const forgotForm = reactive({
}) })
const forgotRules = { const forgotRules = {
email: { rules: { required: true, email: true }, label: 'อีเมล' } email: {
rules: {
required: true,
email: true,
custom: (val: string) => /[\u0E00-\u0E7F]/.test(val) ? 'ห้ามใส่ภาษาไทย' : null
},
label: 'อีเมล'
}
}
const handleInput = (val: string) => {
forgotForm.email = val
if (/[\u0E00-\u0E7F]/.test(val)) {
errors.value.email = 'ห้ามใส่ภาษาไทย'
} else {
if (errors.value.email === 'ห้ามใส่ภาษาไทย') {
clearFieldError('email')
}
}
} }
const { requestPasswordReset } = useAuth() const { requestPasswordReset } = useAuth()
@ -68,29 +86,19 @@ const sendResetLink = async () => {
<form v-if="forgotStep === 'initial'" @submit.prevent="sendResetLink"> <form v-if="forgotStep === 'initial'" @submit.prevent="sendResetLink">
<p class="text-slate-700 dark:text-slate-400 text-sm mb-6">กรณากรอกอเมลเพอรบลงกเซตรหณผาน</p> <p class="text-slate-700 dark:text-slate-400 text-sm mb-6">กรณากรอกอเมลเพอรบลงกเซตรหณผาน</p>
<FormInput <FormInput
v-model="forgotForm.email" :model-value="forgotForm.email"
label="อีเมล" label="อีเมล"
type="email" type="email"
placeholder="student@example.com" placeholder="student@example.com"
:error="errors.email" :error="errors.email"
required required
@update:model-value="clearFieldError('email')" @update:model-value="handleInput"
/> />
<button type="submit" class="btn btn-primary w-full mb-4" :disabled="isLoading"> <button type="submit" class="btn btn-primary w-full mb-4" :disabled="isLoading">
<LoadingSpinner v-if="isLoading" size="sm" /> <LoadingSpinner v-if="isLoading" size="sm" />
<span v-else>ส่งลิงก์รีเซ็ต</span> <span v-else>ส่งลิงก์รีเซ็ต</span>
</button> </button>
</form> </form>
<!-- Step 2: Success Message -->
<div v-else style="text-align: center;">
<div style="font-size: 40px; margin-bottom: 16px;">📧</div>
<h3 class="font-bold mb-2">งลงกเรยบรอยแล</h3>
<p class="text-sm text-muted mb-6">กรณาตรวจสอบกลองจดหมายในอเมลของคณเพอดำเนนการรเซตรหสผาน</p>
<!-- Simulation: redirect to reset-password for demo -->
<NuxtLink to="/reset-password" class="btn btn-primary w-full mb-4">จำลองการคลกลงก (เพอการสาธ)</NuxtLink>
</div>
<!-- Navigation Links --> <!-- Navigation Links -->
<div class="text-center mt-6"> <div class="text-center mt-6">
<NuxtLink to="/auth/login" class="text-sm font-bold text-slate-500 hover:text-slate-800 transition-colors"> <NuxtLink to="/auth/login" class="text-sm font-bold text-slate-500 hover:text-slate-800 transition-colors">

View file

@ -26,12 +26,32 @@ const resetForm = reactive({
}) })
const resetRules = { const resetRules = {
password: { rules: { required: true, minLength: 8 }, label: 'รหัสผ่านใหม่' }, password: {
rules: {
required: true,
minLength: 8,
custom: (val: string) => /[\u0E00-\u0E7F]/.test(val) ? 'ห้ามใส่ภาษาไทย' : null
},
label: 'รหัสผ่านใหม่'
},
confirmPassword: { rules: { required: true, match: 'password' }, label: 'ยืนยันรหัสผ่าน' } confirmPassword: { rules: { required: true, match: 'password' }, label: 'ยืนยันรหัสผ่าน' }
} }
const handlePasswordInput = (field: keyof typeof resetForm, val: string) => {
resetForm[field] = val
if (/[\u0E00-\u0E7F]/.test(val)) {
if (field === 'password') errors.value.password = 'ห้ามใส่ภาษาไทย'
// We don't necessarily need to flag confirmPassword individually if it just needs to match, but let's be consistent if we want
} else {
// Clear error if it was "Thai characters"
if (field === 'password' && errors.value.password === 'ห้ามใส่ภาษาไทย') {
clearFieldError('password')
}
}
}
onMounted(() => { onMounted(() => {
if (!route.query.token || !route.query.id) { if (!route.query.token) {
alert('ลิงก์รีเซ็ตรหัสผ่านไม่ถูกต้องหรือหมดอายุ') alert('ลิงก์รีเซ็ตรหัสผ่านไม่ถูกต้องหรือหมดอายุ')
router.push('/auth/login') router.push('/auth/login')
} }
@ -40,11 +60,10 @@ onMounted(() => {
const resetPassword = async () => { const resetPassword = async () => {
if (!validate(resetForm, resetRules)) return if (!validate(resetForm, resetRules)) return
// Extract token and id from query // Extract token from query
const token = route.query.token as string const token = route.query.token as string
const id = Number(route.query.id)
if (!token || !id) { if (!token) {
alert('ข้อมูลสำหรับรีเซ็ตไม่ครบถ้วน') alert('ข้อมูลสำหรับรีเซ็ตไม่ครบถ้วน')
return return
} }
@ -52,7 +71,6 @@ const resetPassword = async () => {
isLoading.value = true isLoading.value = true
const result = await confirmResetPassword({ const result = await confirmResetPassword({
id,
token, token,
password: resetForm.password password: resetForm.password
}) })
@ -90,13 +108,13 @@ const resetPassword = async () => {
<!-- New Password --> <!-- New Password -->
<FormInput <FormInput
v-model="resetForm.password" :model-value="resetForm.password"
label="รหัสผ่านใหม่" label="รหัสผ่านใหม่"
type="password" type="password"
placeholder="••••••••" placeholder="••••••••"
:error="errors.password" :error="errors.password"
required required
@update:model-value="clearFieldError('password')" @update:model-value="(val) => handlePasswordInput('password', val)"
/> />
<!-- Confirm New Password --> <!-- Confirm New Password -->