Improve profile view and fix app install dialog overflow on mobile

This commit is contained in:
Johannes Zellner
2025-03-17 12:33:27 +01:00
parent e83bcf0fd9
commit b3131169ad
2 changed files with 101 additions and 40 deletions
+99 -38
View File
@@ -8,13 +8,16 @@ import { ref, onMounted, useTemplateRef } from 'vue';
import { Button, Dropdown, Dialog, InputDialog, TextInput } from 'pankow';
import { TOKEN_TYPES } from '../constants.js';
import AppPasswords from '../components/AppPasswords.vue';
import SettingsItem from '../components/SettingsItem.vue';
import Section from '../components/Section.vue';
import ApiTokens from '../components/ApiTokens.vue';
import DashboardModel from '../models/DashboardModel.js';
import ProfileModel from '../models/ProfileModel.js';
import CloudronModel from '../models/CloudronModel.js';
import TokensModel from '../models/TokensModel.js';
const dashboardModel = DashboardModel.create();
const profileModel = ProfileModel.create();
const cloudronModel = CloudronModel.create();
const tokensModel = TokensModel.create();
@@ -129,6 +132,7 @@ async function onPasswordChange() {
if (error) return console.error('Failed to change password', error);
}
// TODO
async function onPasswordReset() {
const error = await profileModel.sendPasswordReset(user.value.email);
if (error) return console.error('Failed to reset password:', error);
@@ -221,6 +225,10 @@ onMounted(async () => {
const usedLang = window.localStorage.NG_TRANSLATE_LANG_KEY || 'en';
language.value = languages.value.find(l => l.id === usedLang).id;
[error, result] = await dashboardModel.config();
if (error) return console.error(error);
config.value = result;
[error, result] = await tokensModel.list();
if (error) return console.error(error);
@@ -261,49 +269,68 @@ onMounted(async () => {
<Button @click="profileModel.logout()" icon="fa fa-sign-out">{{ $t('main.logout') }}</Button>
</template>
<div style="display: flex;">
<div style="display: flex; flex-wrap: wrap">
<div style="width: 150px;">
<input type="file" ref="avatarFileInput" style="display: none" accept="image/*" @change="onAvatarChanged()"/>
<div class="settings-avatar" :style="`background-image: url('${user.avatarUrl}');`" @click="avatarFileInput.click()">
<i class="picture-edit-indicator fa fa-pencil-alt"></i>
<div class="profile-avatar" :style="`background-image: url('${user.avatarUrl}');`" @click="avatarFileInput.click()">
<i class="profile-avatar-edit-indicator fa fa-pencil-alt"></i>
</div>
</div>
<div style="flex-grow: 1;">
<table style="width: 100%;">
<tbody>
<tr>
<td class="text-muted">{{ $t('main.username') }}</td>
<td style="width: 100px; height: 34px;">{{ user.username }}</td>
<td style="width: 32px"></td>
</tr>
<tr>
<td class="text-muted">{{ $t('main.displayName') }}</td>
<td style="white-space: nowrap;">{{ user.displayName }}</td>
<td><Button small tool outline @click="onChangeDisplayName(user.displayName)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
</tr>
<tr>
<td class="text-muted">{{ $t('profile.primaryEmail') }}</td>
<td style="white-space: nowrap;">{{ user.email }}</td>
<td><Button small tool outline @click="onChangeEmail(user.email)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
</tr>
<tr>
<td class="text-muted">{{ $t('profile.passwordRecoveryEmail') }}</td>
<td style="white-space: nowrap;">{{ user.fallbackEmail }}</td>
<td><Button small tool outline @click="onChangeFallbackEmail(user.fallbackEmail)" v-show="!user.source && !config.profileLocked" icon="fa fa-edit text-small" /></td>
</tr>
<tr>
<td class="text-muted">{{ $t('profile.language') }}</td>
<td colspan="2" class="text-right"><Dropdown small tool outline v-model="language" :options="languages" option-label="display" option-key="id" @select="onSelectLanguage"/></td>
</tr>
<tr v-show="!user.source">
<td colspan="3" class="text-right">
<!-- <Button tool @click="onPasswordReset()">{{ $t('profile.passwordResetAction') }}</Button> -->
<Button tool @click="onPasswordChange()">{{ $t('profile.changePasswordAction') }}</Button>
<Button tool v-show="!user.source && !config.external2FA" @click="user.twoFactorAuthenticationEnabled ? onTwoFADisable() : onOpenTwoFASetupDialog()">{{ $t(user.twoFactorAuthenticationEnabled ? 'profile.disable2FAAction' : 'profile.enable2FAAction') }}</Button>
</td>
</tr>
</tbody>
</table>
<SettingsItem>
<FormGroup>
<label>{{ $t('main.username') }}</label>
<div>{{ user.username }}</div>
</FormGroup>
</SettingsItem>
<SettingsItem>
<FormGroup>
<label>{{ $t('main.displayName') }}</label>
<div>{{ user.displayName }}</div>
</FormGroup>
<div style="display: flex; align-items: center">
<!-- TODO translate -->
<Button tool plain @click="onChangeDisplayName(user.displayName)" v-show="!user.source && !config.profileLocked">Edit</Button>
</div>
</SettingsItem>
<SettingsItem>
<FormGroup>
<label>{{ $t('profile.primaryEmail') }}</label>
<div>{{ user.email }}</div>
</FormGroup>
<div style="display: flex; align-items: center">
<!-- TODO translate -->
<Button tool plain @click="displayName(user.email)" v-show="!user.source && !config.profileLocked">Edit</Button>
</div>
</SettingsItem>
<SettingsItem>
<FormGroup>
<label>{{ $t('profile.passwordRecoveryEmail') }}</label>
<div>{{ user.fallbackEmail || 'unset' }}</div>
</FormGroup>
<div style="display: flex; align-items: center">
<!-- TODO translate -->
<Button tool plain @click="onChangeFallbackEmail(user.fallbackEmail)" v-show="!user.source && !config.profileLocked">Edit</Button>
</div>
</SettingsItem>
<SettingsItem>
<div style="display: flex; align-items: center">
<div style="font-weight: bold">{{ $t('profile.language') }}</div>
</div>
<div style="display: flex; align-items: center">
<Dropdown small tool outline v-model="language" :options="languages" option-label="display" option-key="id" @select="onSelectLanguage"/>
</div>
</SettingsItem>
<div style="display: flex; gap: 10px;">
<!-- <Button tool @click="onPasswordReset()">{{ $t('profile.passwordResetAction') }}</Button> -->
<Button tool @click="onPasswordChange()">{{ $t('profile.changePasswordAction') }}</Button>
<Button tool v-show="!user.source && !config.external2FA" @click="user.twoFactorAuthenticationEnabled ? onTwoFADisable() : onOpenTwoFASetupDialog()">{{ $t(user.twoFactorAuthenticationEnabled ? 'profile.disable2FAAction' : 'profile.enable2FAAction') }}</Button>
</div>
</div>
</div>
</Section>
@@ -317,3 +344,37 @@ onMounted(async () => {
</Section>
</div>
</template>
<style scoped>
.profile-avatar {
position: relative;
cursor: pointer;
width: 128px;
height: 128px;
background-position: center;
background-size: 100% 100%;
background-repeat: no-repeat;
border: 1px solid gray;
border-radius: 3px;
margin-bottom: 10px;
}
.profile-avatar-edit-indicator {
position: absolute;
bottom: -4px;
right: -4px;
border-radius: 20px;
padding: 5px;
color: var(--pankow-text-color);
background-color: var(--pankow-input-background-color);
transition: all 250ms;
}
.profile-avatar:hover .profile-avatar-edit-indicator {
color: white;
background: var(--pankow-color-primary);
transform: scale(1.2);
}
</style>