feat: add parent PIN setup functionality and email notifications
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 23s

- Implemented User model updates to include PIN and related fields.
- Created email sender utility for sending verification and reset emails.
- Developed ParentPinSetup component for setting up a parent PIN with verification code.
- Enhanced UserProfile and EntityEditForm components to support new features.
- Updated routing to include PIN setup and authentication checks.
- Added styles for new components and improved existing styles for consistency.
- Introduced loading states and error handling in various components.
This commit is contained in:
2026-01-27 14:47:49 -05:00
parent cd9070ec99
commit 3066d7d356
19 changed files with 852 additions and 257 deletions

View File

@@ -3,8 +3,9 @@ import { ref, nextTick, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { eventBus } from '@/common/eventBus'
import { authenticateParent, isParentAuthenticated, logoutParent } from '../../stores/auth'
import '@/assets/modal.css'
import '@/assets/actions-shared.css'
import '@/assets/styles.css'
import '@/assets/colors.css'
import ModalDialog from './ModalDialog.vue'
const router = useRouter()
const show = ref(false)
@@ -14,6 +15,21 @@ const pinInput = ref<HTMLInputElement | null>(null)
const dropdownOpen = ref(false)
const open = async () => {
// Check if user has a pin
try {
const res = await fetch('/api/user/has-pin', { credentials: 'include' })
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Error checking PIN')
if (!data.has_pin) {
console.log('No PIN set, redirecting to setup')
// Route to PIN setup view
router.push('/parent/pin-setup')
return
}
} catch (e) {
error.value = 'Network error'
return
}
pin.value = ''
error.value = ''
show.value = true
@@ -26,22 +42,36 @@ const close = () => {
error.value = ''
}
const submit = () => {
const submit = async () => {
const isDigits = /^\d{4,6}$/.test(pin.value)
if (!isDigits) {
error.value = 'Enter 46 digits'
return
}
if (pin.value !== '1179') {
error.value = 'Incorrect PIN'
return
try {
const res = await fetch('/api/user/check-pin', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pin: pin.value }),
credentials: 'include',
})
const data = await res.json()
if (!res.ok) {
error.value = data.error || 'Error validating PIN'
return
}
if (!data.valid) {
error.value = 'Incorrect PIN'
return
}
// Authenticate parent and navigate
authenticateParent()
close()
router.push('/parent')
} catch (e) {
error.value = 'Network error'
}
// Authenticate parent and navigate
authenticateParent()
close()
router.push('/parent')
}
const handleLogout = () => {
@@ -117,27 +147,24 @@ onUnmounted(() => {
</div>
</div>
<div v-if="show" class="modal-backdrop" @click.self="close">
<div class="modal">
<h3>Enter parent PIN</h3>
<form @submit.prevent="submit">
<input
ref="pinInput"
v-model="pin"
inputmode="numeric"
pattern="\d*"
maxlength="6"
placeholder="46 digits"
class="pin-input"
/>
<div class="actions">
<button type="button" class="btn btn-secondary" @click="close">Cancel</button>
<button type="submit" class="btn btn-primary">OK</button>
</div>
</form>
<div v-if="error" class="error">{{ error }}</div>
</div>
</div>
<ModalDialog v-if="show" title="Enter parent PIN" @click.self="close" @close="close">
<form @submit.prevent="submit">
<input
ref="pinInput"
v-model="pin"
inputmode="numeric"
pattern="\d*"
maxlength="6"
placeholder="46 digits"
class="pin-input"
/>
<div class="actions modal-actions">
<button type="button" class="btn btn-secondary" @click="close">Cancel</button>
<button type="submit" class="btn btn-primary">OK</button>
</div>
</form>
<div v-if="error" class="error modal-message">{{ error }}</div>
</ModalDialog>
</div>
</template>