feat: Add initial frontend setup including authentication, instructor, and admin course management modules.

This commit is contained in:
Missez 2026-01-28 13:38:54 +07:00
parent 9fd217e1db
commit 19844f343b
16 changed files with 2065 additions and 293 deletions

View file

@ -11,15 +11,32 @@
<div class="flex flex-col md:flex-row gap-8">
<!-- Avatar Section -->
<div class="flex flex-col items-center">
<div class="w-32 h-32 bg-primary-100 rounded-full flex items-center justify-center text-6xl mb-4">
{{ profile.avatar }}
<div class="w-32 h-32 rounded-full flex items-center justify-center text-6xl mb-4 overflow-hidden bg-primary-100">
<img
v-if="profile.avatarUrl"
:key="profile.avatarUrl"
:src="profile.avatarUrl"
alt=""
class="w-full h-full object-cover"
@error="onAvatarError"
/>
<span v-else>{{ profile.avatar }}</span>
</div>
<q-btn
outline
color="primary"
label="เปลี่ยนรูป"
icon="photo_camera"
@click="handleAvatarUpload"
:loading="uploadingAvatar"
@click="triggerAvatarUpload"
/>
<p class="text-xs text-gray-400 mt-2">ขนาดไมเก 5MB</p>
<input
ref="avatarInputRef"
type="file"
accept="image/*"
class="hidden"
@change="handleAvatarUpload"
/>
</div>
@ -283,6 +300,7 @@ const profile = ref({
role: '',
roleName: '',
avatar: '👨‍🏫',
avatarUrl: '' as string | null,
createdAt: ''
});
@ -330,12 +348,67 @@ const formatDate = (date: string) => {
});
};
const handleAvatarUpload = () => {
$q.notify({
type: 'info',
message: 'ฟีเจอร์อัพโหลดรูปภาพจะพร้อมใช้งานเร็วๆ นี้',
position: 'top'
});
// Avatar upload
const avatarInputRef = ref<HTMLInputElement | null>(null);
const uploadingAvatar = ref(false);
const triggerAvatarUpload = () => {
avatarInputRef.value?.click();
};
const onAvatarError = () => {
// Fallback to emoji if image fails
profile.value.avatarUrl = null;
};
const handleAvatarUpload = async (event: Event) => {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;
// Validate file type
if (!file.type.startsWith('image/')) {
$q.notify({
type: 'warning',
message: 'กรุณาเลือกไฟล์รูปภาพเท่านั้น',
position: 'top'
});
return;
}
// Validate file size (max 5MB)
if (file.size > 5 * 1024 * 1024) {
$q.notify({
type: 'warning',
message: 'ไฟล์มีขนาดใหญ่เกิน 5MB',
position: 'top'
});
return;
}
uploadingAvatar.value = true;
try {
await userService.uploadAvatar(file);
// Re-fetch profile to get presigned URL from backend
await fetchProfile();
$q.notify({
type: 'positive',
message: 'อัพโหลดรูปโปรไฟล์สำเร็จ',
position: 'top'
});
} catch (error) {
console.error('Failed to upload avatar:', error);
$q.notify({
type: 'negative',
message: 'เกิดข้อผิดพลาดในการอัพโหลดรูป',
position: 'top'
});
} finally {
uploadingAvatar.value = false;
input.value = '';
}
};
const handleUpdateProfile = async () => {
@ -432,7 +505,8 @@ const fetchProfile = async () => {
phone: data.profile.phone || '',
role: data.role.code,
roleName: data.role.name.th,
avatar: data.profile.avatar_url || '👨‍🏫',
avatar: '👨‍🏫',
avatarUrl: data.profile.avatar_url,
createdAt: data.created_at
};
} catch (error) {