added user profile
This commit is contained in:
@@ -34,6 +34,27 @@
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
|
||||
/* Additional semantic colors */
|
||||
--color-primary: #007bff;
|
||||
--color-primary-dark: #0056b3;
|
||||
--color-bg-secondary: #f8f9fa;
|
||||
--color-text-primary: #333;
|
||||
--color-text-secondary: #495057;
|
||||
|
||||
/* Spacing variables */
|
||||
--padding-xs: 4px;
|
||||
--padding-sm: 8px;
|
||||
--padding-md: 16px;
|
||||
--padding-lg: 24px;
|
||||
--margin-xs: 4px;
|
||||
--margin-sm: 8px;
|
||||
--margin-md: 16px;
|
||||
--margin-lg: 24px;
|
||||
|
||||
/* Other utilities */
|
||||
--border-radius: 6px;
|
||||
--box-shadow-light: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -47,6 +68,11 @@
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
|
||||
/* Dark mode adjustments for additional colors */
|
||||
--color-bg-secondary: #2a2a2a;
|
||||
--color-text-primary: #e9ecef;
|
||||
--color-text-secondary: #adb5bd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
<router-link to="/maintenance" active-class="active">Maintenance</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<LanguageSwitcher />
|
||||
<div class="menu-right">
|
||||
<LanguageSwitcher />
|
||||
<router-link v-if="isLoggedIn" to="/profile" active-class="active" class="profile-link">{{ $t('menu.Profile') }}</router-link>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
@@ -80,4 +83,28 @@ export default {
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.profile-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.profile-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.profile-link.active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -36,6 +36,7 @@ export default {
|
||||
Notes:'Notizen',
|
||||
Campagne:'Kampagne',
|
||||
DeleteChar:'Figur löschen',
|
||||
Profile:'Profil',
|
||||
//Character:'Charakter',
|
||||
},
|
||||
Equipment:'Ausrüstung',
|
||||
@@ -437,5 +438,37 @@ export default {
|
||||
generating: 'PDF wird generiert...',
|
||||
pleaseWait: 'Bitte warten, dies kann einen Moment dauern',
|
||||
popupBlocked: 'Popup wurde blockiert. Bitte erlauben Sie Popups für diese Seite.'
|
||||
},
|
||||
profile: {
|
||||
title: 'Benutzerprofil',
|
||||
loading: 'Lade Profil...',
|
||||
userInfo: 'Benutzerinformationen',
|
||||
username: 'Benutzername',
|
||||
currentEmail: 'Aktuelle E-Mail',
|
||||
changeEmail: 'E-Mail ändern',
|
||||
newEmail: 'Neue E-Mail',
|
||||
emailPlaceholder: 'ihre.email@example.com',
|
||||
updateEmail: 'E-Mail aktualisieren',
|
||||
changePassword: 'Passwort ändern',
|
||||
currentPassword: 'Aktuelles Passwort',
|
||||
currentPasswordPlaceholder: 'Ihr aktuelles Passwort',
|
||||
newPassword: 'Neues Passwort',
|
||||
newPasswordPlaceholder: 'Mindestens 6 Zeichen',
|
||||
confirmPassword: 'Passwort bestätigen',
|
||||
confirmPasswordPlaceholder: 'Neues Passwort wiederholen',
|
||||
updatePassword: 'Passwort aktualisieren',
|
||||
updating: 'Aktualisiere...',
|
||||
loadError: 'Fehler beim Laden des Profils',
|
||||
emailRequired: 'E-Mail-Adresse ist erforderlich',
|
||||
emailUnchanged: 'Die neue E-Mail ist identisch mit der aktuellen',
|
||||
emailUpdateSuccess: 'E-Mail erfolgreich aktualisiert',
|
||||
emailUpdateError: 'Fehler beim Aktualisieren der E-Mail',
|
||||
emailInUse: 'Diese E-Mail-Adresse wird bereits verwendet',
|
||||
allFieldsRequired: 'Alle Felder sind erforderlich',
|
||||
passwordTooShort: 'Das Passwort muss mindestens 6 Zeichen lang sein',
|
||||
passwordMismatch: 'Die Passwörter stimmen nicht überein',
|
||||
passwordUpdateSuccess: 'Passwort erfolgreich aktualisiert',
|
||||
passwordUpdateError: 'Fehler beim Aktualisieren des Passworts',
|
||||
currentPasswordIncorrect: 'Das aktuelle Passwort ist falsch'
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ export default {
|
||||
Notes:'Notes',
|
||||
Campagne:'Campagne',
|
||||
DeleteChar:'Delete Character',
|
||||
Profile:'Profile',
|
||||
//Character:'Charakter',
|
||||
},
|
||||
Equipment:'Equipment',
|
||||
@@ -435,5 +436,37 @@ export default {
|
||||
generating: 'Generating PDF...',
|
||||
pleaseWait: 'Please wait, this may take a moment',
|
||||
popupBlocked: 'Popup was blocked. Please allow popups for this site.'
|
||||
},
|
||||
profile: {
|
||||
title: 'User Profile',
|
||||
loading: 'Loading profile...',
|
||||
userInfo: 'User Information',
|
||||
username: 'Username',
|
||||
currentEmail: 'Current Email',
|
||||
changeEmail: 'Change Email',
|
||||
newEmail: 'New Email',
|
||||
emailPlaceholder: 'your.email@example.com',
|
||||
updateEmail: 'Update Email',
|
||||
changePassword: 'Change Password',
|
||||
currentPassword: 'Current Password',
|
||||
currentPasswordPlaceholder: 'Your current password',
|
||||
newPassword: 'New Password',
|
||||
newPasswordPlaceholder: 'At least 6 characters',
|
||||
confirmPassword: 'Confirm Password',
|
||||
confirmPasswordPlaceholder: 'Repeat new password',
|
||||
updatePassword: 'Update Password',
|
||||
updating: 'Updating...',
|
||||
loadError: 'Failed to load profile',
|
||||
emailRequired: 'Email address is required',
|
||||
emailUnchanged: 'The new email is identical to the current one',
|
||||
emailUpdateSuccess: 'Email updated successfully',
|
||||
emailUpdateError: 'Failed to update email',
|
||||
emailInUse: 'This email address is already in use',
|
||||
allFieldsRequired: 'All fields are required',
|
||||
passwordTooShort: 'Password must be at least 6 characters long',
|
||||
passwordMismatch: 'Passwords do not match',
|
||||
passwordUpdateSuccess: 'Password updated successfully',
|
||||
passwordUpdateError: 'Failed to update password',
|
||||
currentPasswordIncorrect: 'Current password is incorrect'
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import DashboardView from "../views/DashboardView.vue";
|
||||
import AusruestungView from "../views/AusruestungView.vue";
|
||||
import MaintenanceView from "../views/MaintenanceView.vue";
|
||||
import FileUploadPage from "../views/FileUploadPage.vue";
|
||||
import UserProfileView from "../views/UserProfileView.vue";
|
||||
|
||||
import CharacterDetails from "@/components/CharacterDetails.vue";
|
||||
import CharacterCreation from "@/components/CharacterCreation.vue";
|
||||
@@ -21,6 +22,7 @@ const routes = [
|
||||
{ path: "/forgot-password", name: "ForgotPassword", component: ForgotPasswordView },
|
||||
{ path: "/reset-password", name: "ResetPassword", component: ResetPasswordView },
|
||||
{ path: "/dashboard", name: "Dashboard", component: DashboardView, meta: { requiresAuth: true } },
|
||||
{ path: "/profile", name: "UserProfile", component: UserProfileView, meta: { requiresAuth: true } },
|
||||
{ path: "/ausruestung/:characterId", name: "Ausruestung", component: AusruestungView, meta: { requiresAuth: true } },
|
||||
{ path: "/maintenance", name: "Maintenance", component: MaintenanceView, meta: { requiresAuth: true } },
|
||||
{ path: "/upload", name: "FileUpload", component: FileUploadPage },
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<div class="user-profile">
|
||||
<div class="container">
|
||||
<h1>{{ $t('profile.title') }}</h1>
|
||||
|
||||
<div v-if="loading" class="loading">
|
||||
{{ $t('profile.loading') }}
|
||||
</div>
|
||||
|
||||
<div v-else class="profile-sections">
|
||||
<!-- User Information Section -->
|
||||
<div class="profile-section">
|
||||
<h2>{{ $t('profile.userInfo') }}</h2>
|
||||
<div class="info-row">
|
||||
<label>{{ $t('profile.username') }}:</label>
|
||||
<span>{{ userProfile.username }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<label>{{ $t('profile.currentEmail') }}:</label>
|
||||
<span>{{ userProfile.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Email Section -->
|
||||
<div class="profile-section">
|
||||
<h2>{{ $t('profile.changeEmail') }}</h2>
|
||||
<form @submit.prevent="updateEmail" class="profile-form">
|
||||
<div class="form-group">
|
||||
<label for="newEmail">{{ $t('profile.newEmail') }}:</label>
|
||||
<input
|
||||
type="email"
|
||||
id="newEmail"
|
||||
v-model="emailForm.newEmail"
|
||||
:placeholder="$t('profile.emailPlaceholder')"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" :disabled="isUpdating" class="btn-primary">
|
||||
<span v-if="!isUpdating">{{ $t('profile.updateEmail') }}</span>
|
||||
<span v-else>{{ $t('profile.updating') }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Change Password Section -->
|
||||
<div class="profile-section">
|
||||
<h2>{{ $t('profile.changePassword') }}</h2>
|
||||
<form @submit.prevent="updatePassword" class="profile-form">
|
||||
<div class="form-group">
|
||||
<label for="currentPassword">{{ $t('profile.currentPassword') }}:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="currentPassword"
|
||||
v-model="passwordForm.currentPassword"
|
||||
:placeholder="$t('profile.currentPasswordPlaceholder')"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newPassword">{{ $t('profile.newPassword') }}:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="newPassword"
|
||||
v-model="passwordForm.newPassword"
|
||||
:placeholder="$t('profile.newPasswordPlaceholder')"
|
||||
minlength="6"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword">{{ $t('profile.confirmPassword') }}:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
v-model="passwordForm.confirmPassword"
|
||||
:placeholder="$t('profile.confirmPasswordPlaceholder')"
|
||||
minlength="6"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button type="submit" :disabled="isUpdating" class="btn-primary">
|
||||
<span v-if="!isUpdating">{{ $t('profile.updatePassword') }}</span>
|
||||
<span v-else>{{ $t('profile.updating') }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.user-profile {
|
||||
padding: var(--padding-lg);
|
||||
margin-top: 2%;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--margin-lg);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: var(--padding-lg);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.profile-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--margin-lg);
|
||||
}
|
||||
|
||||
.profile-section {
|
||||
background-color: var(--color-bg-secondary);
|
||||
padding: var(--padding-md);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow-light);
|
||||
}
|
||||
|
||||
.profile-section h2 {
|
||||
color: var(--color-primary);
|
||||
margin-bottom: var(--margin-md);
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
padding: var(--padding-sm) 0;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.info-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-row label {
|
||||
font-weight: bold;
|
||||
width: 200px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.info-row span {
|
||||
flex: 1;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.profile-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--margin-md);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--margin-xs);
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-weight: bold;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
padding: var(--padding-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
padding: var(--padding-sm) var(--padding-md);
|
||||
border: none;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import API from '../utils/api'
|
||||
|
||||
export default {
|
||||
name: 'UserProfileView',
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
isUpdating: false,
|
||||
userProfile: {
|
||||
username: '',
|
||||
email: ''
|
||||
},
|
||||
emailForm: {
|
||||
newEmail: ''
|
||||
},
|
||||
passwordForm: {
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadProfile()
|
||||
},
|
||||
methods: {
|
||||
async loadProfile() {
|
||||
this.loading = true
|
||||
try {
|
||||
const response = await API.get('/api/user/profile')
|
||||
this.userProfile = response.data
|
||||
this.emailForm.newEmail = this.userProfile.email
|
||||
} catch (error) {
|
||||
console.error('Failed to load profile:', error)
|
||||
alert(this.$t('profile.loadError') + ': ' + (error.response?.data?.error || error.message))
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async updateEmail() {
|
||||
if (!this.emailForm.newEmail) {
|
||||
alert(this.$t('profile.emailRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
if (this.emailForm.newEmail === this.userProfile.email) {
|
||||
alert(this.$t('profile.emailUnchanged'))
|
||||
return
|
||||
}
|
||||
|
||||
this.isUpdating = true
|
||||
try {
|
||||
const response = await API.put('/api/user/email', {
|
||||
email: this.emailForm.newEmail
|
||||
})
|
||||
|
||||
this.userProfile.email = response.data.email
|
||||
alert(this.$t('profile.emailUpdateSuccess'))
|
||||
} catch (error) {
|
||||
console.error('Failed to update email:', error)
|
||||
let errorMsg = this.$t('profile.emailUpdateError')
|
||||
if (error.response?.data?.error) {
|
||||
if (error.response.data.error.includes('already in use')) {
|
||||
errorMsg = this.$t('profile.emailInUse')
|
||||
} else {
|
||||
errorMsg += ': ' + error.response.data.error
|
||||
}
|
||||
}
|
||||
alert(errorMsg)
|
||||
} finally {
|
||||
this.isUpdating = false
|
||||
}
|
||||
},
|
||||
async updatePassword() {
|
||||
if (!this.passwordForm.currentPassword || !this.passwordForm.newPassword || !this.passwordForm.confirmPassword) {
|
||||
alert(this.$t('profile.allFieldsRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
if (this.passwordForm.newPassword.length < 6) {
|
||||
alert(this.$t('profile.passwordTooShort'))
|
||||
return
|
||||
}
|
||||
|
||||
if (this.passwordForm.newPassword !== this.passwordForm.confirmPassword) {
|
||||
alert(this.$t('profile.passwordMismatch'))
|
||||
return
|
||||
}
|
||||
|
||||
this.isUpdating = true
|
||||
try {
|
||||
await API.put('/api/user/password', {
|
||||
current_password: this.passwordForm.currentPassword,
|
||||
new_password: this.passwordForm.newPassword
|
||||
})
|
||||
|
||||
alert(this.$t('profile.passwordUpdateSuccess'))
|
||||
|
||||
// Clear password fields
|
||||
this.passwordForm.currentPassword = ''
|
||||
this.passwordForm.newPassword = ''
|
||||
this.passwordForm.confirmPassword = ''
|
||||
} catch (error) {
|
||||
console.error('Failed to update password:', error)
|
||||
let errorMsg = this.$t('profile.passwordUpdateError')
|
||||
if (error.response?.data?.error) {
|
||||
if (error.response.data.error.includes('incorrect')) {
|
||||
errorMsg = this.$t('profile.currentPasswordIncorrect')
|
||||
} else {
|
||||
errorMsg += ': ' + error.response.data.error
|
||||
}
|
||||
}
|
||||
alert(errorMsg)
|
||||
} finally {
|
||||
this.isUpdating = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user