Files
Web-Antrean/pages/Profile/Profil.vue

800 lines
25 KiB
Vue

<template>
<v-container fluid class="pa-0">
<!-- Hero Header Section -->
<div class="hero-header">
<v-container class="py-8">
<div class="d-flex align-center">
<v-icon color="white" size="40" class="mr-3">mdi-account-circle</v-icon>
<div>
<h1 class="text-h4 font-weight-bold text-white mb-1">Profil Saya</h1>
<p class="text-body-1 text-white opacity-90">Kelola informasi profil dan pengaturan akun Anda</p>
</div>
</div>
</v-container>
</div>
<v-container class="content-container pb-12">
<v-row>
<!-- Left Sidebar - Profile Card -->
<v-col cols="12" lg="4">
<v-card class="rounded-xl elevation-8 sticky-card profile-card">
<div class="text-center profile-content">
<div class="profile-avatar-container">
<v-avatar size="140" class="profile-avatar elevation-8">
<v-img
:src="profileData.picture || 'https://i.pravatar.cc/300?img=68'"
alt="Profile Picture"
></v-img>
</v-avatar>
<v-btn
icon
size="small"
color="orange-darken-2"
class="avatar-edit-btn elevation-4"
@click="openPhotoDialog"
>
<v-icon size="18">mdi-camera</v-icon>
</v-btn>
</div>
<h2 class="text-h5 font-weight-bold mb-2">{{ profileData.name }}</h2>
<p class="text-body-2 text-grey-darken-1 mb-1">@{{ profileData.username }}</p>
<p class="text-body-2 text-grey mb-4">{{ profileData.email }}</p>
<v-chip
color="success"
variant="flat"
size="small"
prepend-icon="mdi-check-circle"
class="mb-4"
>
Akun Terverifikasi
</v-chip>
<v-divider class="my-4"></v-divider>
<!-- Quick Info -->
<div class="text-left px-6">
<div class="info-item mb-3">
<v-icon size="20" color="blue-darken-2" class="mr-2">mdi-identifier</v-icon>
<span class="text-body-2 text-grey-darken-1">ID: {{ profileData.id }}</span>
</div>
<div class="info-item mb-3">
<v-icon size="20" color="orange-darken-2" class="mr-2">mdi-calendar-check</v-icon>
<span class="text-body-2 text-grey-darken-1">Bergabung: {{ profileData.joinDate }}</span>
</div>
<div class="info-item">
<v-icon size="20" color="green-darken-2" class="mr-2">mdi-clock-outline</v-icon>
<span class="text-body-2 text-grey-darken-1">Login: {{ profileData.lastLogin }}</span>
</div>
</div>
</div>
<v-card-actions class="pa-4">
<v-btn
color="blue-darken-2"
variant="outlined"
block
class="rounded-lg"
prepend-icon="mdi-cog"
@click="navigateTo('/Profile/Pengaturan')"
>
Pengaturan Akun
</v-btn>
</v-card-actions>
</v-card>
</v-col>
<!-- Right Content - Profile Details -->
<v-col cols="12" lg="8">
<!-- Personal Information -->
<v-card class="rounded-xl elevation-4 mb-4">
<v-card-title class="d-flex align-center pa-6 pb-4">
<v-icon color="blue-darken-2" class="mr-2">mdi-account-details</v-icon>
<span class="font-weight-bold">Informasi Pribadi</span>
<v-spacer></v-spacer>
<v-btn
v-if="!isEditing"
color="orange-darken-2"
variant="flat"
size="small"
class="rounded-lg"
prepend-icon="mdi-pencil"
@click="startEdit"
>
Edit
</v-btn>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="pa-6">
<v-form ref="profileForm">
<v-row>
<v-col cols="12" md="6">
<label class="text-caption text-grey-darken-1 font-weight-bold mb-1 d-block">NAMA LENGKAP</label>
<v-text-field
v-model="profileData.name"
placeholder="Masukkan nama lengkap"
prepend-inner-icon="mdi-account"
variant="outlined"
density="comfortable"
color="blue-darken-2"
:readonly="!isEditing"
hide-details="auto"
class="mb-4"
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<label class="text-caption text-grey-darken-1 font-weight-bold mb-1 d-block">USERNAME</label>
<v-text-field
v-model="profileData.username"
placeholder="Masukkan username"
prepend-inner-icon="mdi-at"
variant="outlined"
density="comfortable"
color="blue-darken-2"
:readonly="!isEditing"
hide-details="auto"
class="mb-4"
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<label class="text-caption text-grey-darken-1 font-weight-bold mb-1 d-block">EMAIL</label>
<v-text-field
v-model="profileData.email"
placeholder="Masukkan email"
prepend-inner-icon="mdi-email"
variant="outlined"
density="comfortable"
color="blue-darken-2"
:readonly="!isEditing"
hide-details="auto"
class="mb-4"
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<label class="text-caption text-grey-darken-1 font-weight-bold mb-1 d-block">NOMOR TELEPON</label>
<v-text-field
v-model="profileData.phone"
placeholder="Masukkan nomor telepon"
prepend-inner-icon="mdi-phone"
variant="outlined"
density="comfortable"
color="blue-darken-2"
:readonly="!isEditing"
hide-details="auto"
class="mb-4"
></v-text-field>
</v-col>
<v-col cols="12">
<label class="text-caption text-grey-darken-1 font-weight-bold mb-1 d-block">BIO</label>
<v-textarea
v-model="profileData.bio"
placeholder="Ceritakan tentang diri Anda"
prepend-inner-icon="mdi-text"
variant="outlined"
color="blue-darken-2"
rows="3"
:readonly="!isEditing"
hide-details="auto"
></v-textarea>
</v-col>
</v-row>
</v-form>
</v-card-text>
<v-divider v-if="isEditing"></v-divider>
<v-card-actions v-if="isEditing" class="pa-6 pt-4">
<v-spacer></v-spacer>
<v-btn
variant="outlined"
color="grey-darken-1"
class="rounded-lg px-6"
@click="cancelEdit"
>
Batal
</v-btn>
<v-btn
color="blue-darken-2"
variant="flat"
class="rounded-lg px-8"
prepend-icon="mdi-check"
@click="saveProfile"
:loading="isSaving"
>
Simpan Perubahan
</v-btn>
</v-card-actions>
</v-card>
<!-- Security Section -->
<v-card class="rounded-xl elevation-4 mb-4">
<v-card-title class="d-flex align-center pa-6 pb-4">
<v-icon color="orange-darken-2" class="mr-2">mdi-shield-check</v-icon>
<span class="font-weight-bold">Keamanan</span>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="pa-6">
<v-list class="transparent">
<v-list-item
class="rounded-lg mb-2 px-4"
prepend-icon="mdi-lock-reset"
@click="openPasswordDialog"
>
<v-list-item-title class="font-weight-medium">Ubah Password</v-list-item-title>
<v-list-item-subtitle class="text-caption">Perbarui password akun Anda</v-list-item-subtitle>
<template v-slot:append>
<v-icon color="grey">mdi-chevron-right</v-icon>
</template>
</v-list-item>
<v-list-item
class="rounded-lg mb-2 px-4"
prepend-icon="mdi-two-factor-authentication"
>
<v-list-item-title class="font-weight-medium">Autentikasi Dua Faktor</v-list-item-title>
<v-list-item-subtitle class="text-caption">Tingkatkan keamanan akun</v-list-item-subtitle>
<template v-slot:append>
<v-switch
v-model="twoFactorEnabled"
color="blue-darken-2"
hide-details
inset
@change="toggleTwoFactor"
></v-switch>
</template>
</v-list-item>
<v-list-item
class="rounded-lg px-4"
prepend-icon="mdi-devices"
@click="openDevicesDialog"
>
<v-list-item-title class="font-weight-medium">Perangkat Aktif</v-list-item-title>
<v-list-item-subtitle class="text-caption">Kelola perangkat yang terhubung</v-list-item-subtitle>
<template v-slot:append>
<v-chip size="small" color="success" variant="flat">{{ activeSessions.length }} Perangkat</v-chip>
</template>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
<!-- Preferences Section -->
<v-card class="rounded-xl elevation-4">
<v-card-title class="d-flex align-center pa-6 pb-4">
<v-icon color="blue-darken-2" class="mr-2">mdi-tune</v-icon>
<span class="font-weight-bold">Preferensi</span>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="pa-6">
<v-list class="transparent">
<v-list-item class="rounded-lg mb-2 px-4" prepend-icon="mdi-bell">
<v-list-item-title class="font-weight-medium">Notifikasi Email</v-list-item-title>
<v-list-item-subtitle class="text-caption">Terima update via email</v-list-item-subtitle>
<template v-slot:append>
<v-switch
v-model="emailNotifications"
color="orange-darken-2"
hide-details
inset
></v-switch>
</template>
</v-list-item>
<v-list-item class="rounded-lg mb-2 px-4" prepend-icon="mdi-theme-light-dark">
<v-list-item-title class="font-weight-medium">Tema Gelap</v-list-item-title>
<v-list-item-subtitle class="text-caption">Aktifkan mode gelap</v-list-item-subtitle>
<template v-slot:append>
<v-switch
v-model="darkMode"
color="blue-darken-2"
hide-details
inset
></v-switch>
</template>
</v-list-item>
<v-list-item class="rounded-lg px-4" prepend-icon="mdi-web">
<v-list-item-title class="font-weight-medium">Bahasa</v-list-item-title>
<v-list-item-subtitle class="text-caption">Indonesia</v-list-item-subtitle>
<template v-slot:append>
<v-icon color="grey">mdi-chevron-right</v-icon>
</template>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
Change Password Dialog
<v-dialog v-model="passwordDialog" max-width="500" persistent>
<v-card class="rounded-xl">
<v-card-title class="pa-6 pb-4">
<div class="d-flex align-center">
<v-icon color="orange-darken-2" class="mr-2">mdi-lock-reset</v-icon>
<span class="font-weight-bold">Ubah Password</span>
</div>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="pa-6">
<v-form ref="passwordForm">
<label class="text-caption text-grey-darken-1 font-weight-bold mb-1 d-block">PASSWORD SAAT INI</label>
<v-text-field
v-model="passwordData.current"
type="password"
prepend-inner-icon="mdi-lock"
variant="outlined"
density="comfortable"
color="blue-darken-2"
hide-details="auto"
class="mb-4"
></v-text-field>
<label class="text-caption text-grey-darken-1 font-weight-bold mb-1 d-block">PASSWORD BARU</label>
<v-text-field
v-model="passwordData.new"
type="password"
prepend-inner-icon="mdi-lock-plus"
variant="outlined"
density="comfortable"
color="blue-darken-2"
hide-details="auto"
class="mb-4"
></v-text-field>
<label class="text-caption text-grey-darken-1 font-weight-bold mb-1 d-block">KONFIRMASI PASSWORD</label>
<v-text-field
v-model="passwordData.confirm"
type="password"
prepend-inner-icon="mdi-lock-check"
variant="outlined"
density="comfortable"
color="blue-darken-2"
hide-details="auto"
></v-text-field>
</v-form>
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pa-6 pt-4">
<v-spacer></v-spacer>
<v-btn
variant="outlined"
color="grey-darken-1"
class="rounded-lg"
@click="passwordDialog = false"
>
Batal
</v-btn>
<v-btn
color="orange-darken-2"
variant="flat"
class="rounded-lg px-6"
@click="changePassword"
:loading="isChangingPassword"
>
Ubah Password
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Active Devices Dialog -->
<v-dialog v-model="devicesDialog" max-width="700" scrollable>
<v-card class="rounded-xl">
<v-card-title class="pa-6 pb-4">
<div class="d-flex align-center">
<v-icon color="blue-darken-2" class="mr-2">mdi-devices</v-icon>
<span class="font-weight-bold">Perangkat Aktif</span>
</div>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="pa-0" style="max-height: 500px;">
<v-list class="py-0">
<v-list-item
v-for="(session, index) in activeSessions"
:key="index"
class="py-4 px-6"
>
<template v-slot:prepend>
<v-avatar :color="session.current ? 'success' : 'blue-grey-lighten-4'" size="48">
<v-icon :color="session.current ? 'white' : 'blue-grey-darken-2'">
{{ session.icon }}
</v-icon>
</v-avatar>
</template>
<v-list-item-title class="font-weight-bold mb-1">
{{ session.device }}
<v-chip v-if="session.current" size="x-small" color="success" class="ml-2">
Sesi Ini
</v-chip>
</v-list-item-title>
<v-list-item-subtitle class="text-caption">
<div>{{ session.location }}</div>
<div class="text-grey-darken-1 mt-1">
<v-icon size="12">mdi-clock-outline</v-icon>
{{ session.lastActive }}
</div>
</v-list-item-subtitle>
<template v-slot:append v-if="!session.current">
<v-btn
icon="mdi-close-circle"
size="small"
variant="text"
color="error"
@click="removeSession(index)"
></v-btn>
</template>
</v-list-item>
</v-list>
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pa-6 pt-4">
<v-btn
color="error"
variant="outlined"
class="rounded-lg"
prepend-icon="mdi-logout-variant"
@click="logoutAllDevices"
>
Keluar dari Semua Perangkat
</v-btn>
<v-spacer></v-spacer>
<v-btn
variant="text"
class="rounded-lg"
@click="devicesDialog = false"
>
Tutup
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Photo Upload Dialog -->
<v-dialog v-model="photoDialog" max-width="400">
<v-card class="rounded-xl">
<v-card-title class="pa-6 pb-4">
<div class="d-flex align-center">
<v-icon color="blue-darken-2" class="mr-2">mdi-camera</v-icon>
<span class="font-weight-bold">Ubah Foto Profil</span>
</div>
</v-card-title>
<v-divider></v-divider>
<v-card-text class="pa-6 text-center">
<v-avatar size="150" class="mb-4">
<v-img :src="profileData.picture"></v-img>
</v-avatar>
<v-file-input
label="Pilih foto baru"
variant="outlined"
prepend-icon=""
prepend-inner-icon="mdi-image"
accept="image/*"
hide-details
></v-file-input>
</v-card-text>
<v-divider></v-divider>
<v-card-actions class="pa-6 pt-4">
<v-btn
color="error"
variant="text"
class="rounded-lg"
>
Hapus Foto
</v-btn>
<v-spacer></v-spacer>
<v-btn
variant="outlined"
color="grey-darken-1"
class="rounded-lg"
@click="photoDialog = false"
>
Batal
</v-btn>
<v-btn
color="blue-darken-2"
variant="flat"
class="rounded-lg px-6"
>
Simpan
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Success Snackbar -->
<v-snackbar
v-model="snackbar"
:color="snackbarColor"
location="top"
timeout="3000"
class="snackbar-custom"
>
<div class="d-flex align-center">
<v-icon class="mr-2">{{ snackbarIcon }}</v-icon>
<span>{{ snackbarMessage }}</span>
</div>
<template v-slot:actions>
<v-btn
variant="text"
size="small"
icon="mdi-close"
@click="snackbar = false"
></v-btn>
</template>
</v-snackbar>
</v-container>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
import { navigateTo } from '#app';
definePageMeta({
middleware: 'auth'
});
const isEditing = ref(false);
const isSaving = ref(false);
const isChangingPassword = ref(false);
const snackbar = ref(false);
const snackbarMessage = ref('');
const snackbarColor = ref('success');
const passwordDialog = ref(false);
const devicesDialog = ref(false);
const photoDialog = ref(false);
const twoFactorEnabled = ref(false);
const emailNotifications = ref(true);
const darkMode = ref(false);
const snackbarIcon = computed(() => {
return snackbarColor.value === 'success' ? 'mdi-check-circle' : 'mdi-alert-circle';
});
const profileData = reactive({
id: 'USR-12345678',
name: 'John Doe',
username: 'johndoe',
email: 'john.doe@example.com',
phone: '+62 812 3456 7890',
bio: 'Passionate developer and tech enthusiast. Love building beautiful and functional web applications.',
picture: 'https://i.pravatar.cc/300?img=68',
joinDate: '15 Januari 2024',
lastLogin: '16 Oktober 2025, 10:30 AM'
});
const passwordData = reactive({
current: '',
new: '',
confirm: ''
});
const activeSessions = ref([
{
device: 'Chrome on Windows 11',
location: 'Sidoarjo, Indonesia',
lastActive: 'Sekarang',
icon: 'mdi-laptop',
current: true
},
{
device: 'Mobile App on Android',
location: 'Surabaya, Indonesia',
lastActive: '2 jam yang lalu',
icon: 'mdi-cellphone',
current: false
},
{
device: 'Safari on macOS',
location: 'Jakarta, Indonesia',
lastActive: '1 hari yang lalu',
icon: 'mdi-laptop',
current: false
}
]);
const originalData = ref({});
const startEdit = () => {
originalData.value = { ...profileData };
isEditing.value = true;
};
const cancelEdit = () => {
Object.assign(profileData, originalData.value);
isEditing.value = false;
};
const saveProfile = async () => {
isSaving.value = true;
try {
await new Promise(resolve => setTimeout(resolve, 1000));
// TODO: Replace with actual API call
// await $fetch('/api/profile/update', {
// method: 'PUT',
// body: profileData
// });
originalData.value = { ...profileData };
isEditing.value = false;
snackbarMessage.value = 'Profil berhasil diperbarui!';
snackbarColor.value = 'success';
snackbar.value = true;
} catch (error) {
snackbarMessage.value = 'Gagal memperbarui profil!';
snackbarColor.value = 'error';
snackbar.value = true;
} finally {
isSaving.value = false;
}
};
const openPhotoDialog = () => {
photoDialog.value = true;
};
const openPasswordDialog = () => {
passwordDialog.value = true;
passwordData.current = '';
passwordData.new = '';
passwordData.confirm = '';
};
const changePassword = async () => {
if (passwordData.new !== passwordData.confirm) {
snackbarMessage.value = 'Password baru tidak cocok!';
snackbarColor.value = 'error';
snackbar.value = true;
return;
}
isChangingPassword.value = true;
try {
await new Promise(resolve => setTimeout(resolve, 1000));
// TODO: Replace with actual API call
passwordDialog.value = false;
snackbarMessage.value = 'Password berhasil diubah!';
snackbarColor.value = 'success';
snackbar.value = true;
} catch (error) {
snackbarMessage.value = 'Gagal mengubah password!';
snackbarColor.value = 'error';
snackbar.value = true;
} finally {
isChangingPassword.value = false;
}
};
const openDevicesDialog = () => {
devicesDialog.value = true;
};
const removeSession = (index) => {
activeSessions.value.splice(index, 1);
snackbarMessage.value = 'Perangkat berhasil dihapus dari sesi aktif';
snackbarColor.value = 'success';
snackbar.value = true;
};
const logoutAllDevices = () => {
// Keep only current session
activeSessions.value = activeSessions.value.filter(s => s.current);
devicesDialog.value = false;
snackbarMessage.value = 'Berhasil keluar dari semua perangkat lain';
snackbarColor.value = 'success';
snackbar.value = true;
};
const toggleTwoFactor = () => {
const status = twoFactorEnabled.value ? 'diaktifkan' : 'dinonaktifkan';
snackbarMessage.value = `Autentikasi dua faktor ${status}`;
snackbarColor.value = 'info';
snackbar.value = true;
};
// Initialize original data
originalData.value = { ...profileData };
</script>
<style scoped>
.hero-header {
background: linear-gradient(135deg, #1976d2 0%, #1565c0 50%, #f57c00 100%);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
position: relative;
z-index: 1;
}
.content-container {
margin-top: 40px;
}
.sticky-card {
position: sticky;
top: 80px;
}
.profile-card {
overflow: visible;
}
.profile-content {
padding-top: 0;
padding-bottom: 20px;
}
.profile-avatar-container {
position: relative;
display: inline-block;
margin-top: -70px;
margin-bottom: 20px;
}
.profile-avatar {
border: 6px solid white;
background: white;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}
.avatar-edit-btn {
position: absolute;
bottom: 8px;
right: 8px;
width: 32px;
height: 32px;
}
.stat-item {
text-align: center;
}
.info-item {
display: flex;
align-items: center;
}
label {
letter-spacing: 0.5px;
}
.v-list-item {
transition: background-color 0.2s;
}
.v-list-item:hover {
background-color: rgba(0, 0, 0, 0.03);
}
.snackbar-custom {
font-weight: 500;
}
@media (max-width: 1280px) {
.sticky-card {
position: relative;
top: 0;
}
}
</style>