feat: Implement initial e-learning platform frontend structure including dashboard, course management, authentication, and common UI components.

This commit is contained in:
supalerk-ar66 2026-02-27 10:05:33 +07:00
parent aceeb80d9a
commit ad11c6b7c5
44 changed files with 720 additions and 578 deletions

View file

@ -67,22 +67,22 @@ const showPassword = reactive({
})
// Rules have been moved to components
// (Rules have been moved to components)
const fileInput = ref<HTMLInputElement | null>(null) // Used in view mode (outside component)
const fileInput = ref<HTMLInputElement | null>(null) // () (Used in view mode (outside component))
const toggleEdit = (edit: boolean) => {
isEditing.value = edit
}
// Updated to accept File object directly (or Event for view mode compatibility if needed)
// File ( Event ) (Updated to accept File object directly (or Event for view mode compatibility if needed))
const handleFileUpload = async (fileOrEvent: File | Event) => {
let file: File | null = null
if (fileOrEvent instanceof File) {
file = fileOrEvent
} else {
// Fallback for native input change event
// input change (Fallback for native input change event)
const target = (fileOrEvent as Event).target as HTMLInputElement
if (target.files && target.files[0]) {
file = target.files[0]
@ -112,7 +112,7 @@ const handleFileUpload = async (fileOrEvent: File | Event) => {
}
}
// Trigger upload for VIEW mode avatar click
// View (Trigger upload for VIEW mode avatar click)
const triggerUpload = () => {
fileInput.value?.click()
}
@ -191,7 +191,7 @@ const handleUpdatePassword = async () => {
isPasswordSaving.value = false
}
// Watch for changes in global user state (e.g. after avatar upload or profile update)
// ( ) (Watch for changes in global user state (e.g. after avatar upload or profile update))
watch(() => currentUser.value, (newUser) => {
if (newUser) {
userData.value.photoURL = newUser.photoURL || ''
@ -220,7 +220,7 @@ onMounted(async () => {
<q-spinner size="3rem" color="primary" />
</div>
<!-- MAIN PROFILE SETTINGS -->
<!-- การตงคาโปรไฟลหล (MAIN PROFILE SETTINGS) -->
<div v-else class="max-w-5xl mx-auto pb-20 fade-in pt-4">
<!-- ตรขอมลโปรไฟล (Profile Card) -->
@ -255,7 +255,7 @@ onMounted(async () => {
</div>
</div>
<!-- Form Inputs (2 Column Grid) -->
<!-- ลดอมลฟอร (แบงเป 2 คอลมน) (Form Inputs (2 Column Grid)) -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-4">
<div class="md:col-span-2 relative">
<label class="block text-[13px] font-bold text-slate-700 dark:text-slate-300 mb-2">{{ $t('profile.prefix') }}</label>
@ -300,7 +300,7 @@ onMounted(async () => {
</div>
<!-- Footer Buttons -->
<!-- มกดยนยนตางๆ (Footer Buttons) -->
<div class="px-6 sm:px-8 py-5 border-t border-slate-200 dark:border-slate-800 flex flex-col sm:flex-row justify-center sm:justify-end gap-3 items-center bg-white dark:!bg-slate-900">
<button class="w-full sm:w-auto text-[13px] font-bold text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white px-4 py-2 transition order-2 sm:order-1" @click="fetchUserProfile(true)">{{ $t('common.cancel') }}</button>
<button @click="handleUpdateProfile" :disabled="isProfileSaving" class="w-full sm:w-auto bg-[#3B6BE8] hover:bg-blue-700 text-white px-6 py-2.5 rounded-lg text-[13px] font-bold transition shadow-sm disabled:opacity-50 order-1 sm:order-2">
@ -310,7 +310,7 @@ onMounted(async () => {
</div>
</div>
<!-- Security Card -->
<!-- การดความปลอดภ (Security Card) -->
<div class="bg-white dark:!bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl shadow-sm overflow-hidden">
<div class="p-8 border-b border-slate-200 dark:border-slate-800">
<h2 class="text-xl font-bold text-slate-900 dark:text-white">{{ $t('profile.security') }}</h2>
@ -337,7 +337,7 @@ onMounted(async () => {
</div>
<!-- Password Modal -->
<!-- โมดอลเปลยนรหสผาน (Password Modal) -->
<q-dialog v-model="showPasswordModal">
<q-card class="w-full max-w-md rounded-2xl p-2 dark:bg-slate-900 shadow-xl">
<q-form @submit="handleUpdatePassword">