Skip to content

Commit 74bdc4f

Browse files
committed
improve Profile view even more
1 parent 94d8d67 commit 74bdc4f

4 files changed

Lines changed: 123 additions & 47 deletions

File tree

client/src/components/profile/index.vue

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -47,47 +47,47 @@
4747
<v-col cols="12" md="6" lg="8">
4848
<v-card class="pa-4">
4949
<h3 class="mb-4">Profile Details</h3>
50-
<v-list dense>
51-
<v-list-item>
52-
<v-list-item-title>First Name</v-list-item-title>
53-
<v-list-item-subtitle>{{ user.firstName }}</v-list-item-subtitle>
54-
</v-list-item>
55-
<v-list-item>
56-
<v-list-item-title>Last Name</v-list-item-title>
57-
<v-list-item-subtitle>{{ user.lastName }}</v-list-item-subtitle>
58-
</v-list-item>
59-
<v-list-item>
60-
<v-list-item-title>Email</v-list-item-title>
61-
<v-list-item-subtitle>{{ user.email }}</v-list-item-subtitle>
62-
</v-list-item>
63-
<v-list-item>
64-
<v-list-item-title>Username</v-list-item-title>
65-
<v-list-item-subtitle>{{ user.username }}</v-list-item-subtitle>
66-
</v-list-item>
67-
<v-list-item>
68-
<v-list-item-title>Role</v-list-item-title>
69-
70-
<v-chip
71-
class="ma-2"
72-
color="primary"
73-
label
74-
v-if="user.role"
75-
>
76-
<v-icon icon="mdi-account-circle-outline" start></v-icon>
77-
{{ user.role.name}}
78-
</v-chip>
79-
<span v-else>-</span>
80-
</v-list-item>
81-
<v-list-item>
82-
<v-list-item-title>Groups</v-list-item-title>
83-
<v-chip v-for="group in user.userGroups" :key="group.id" class="ma-1" color="grey">{{ group.name }}</v-chip>
84-
<span v-if="!user.userGroups || user.userGroups.length === 0">-</span>
85-
</v-list-item>
86-
<v-list-item>
87-
<v-list-item-title>Provider</v-list-item-title>
88-
<v-list-item-subtitle>{{ user.provider || 'local' }}</v-list-item-subtitle>
89-
</v-list-item>
90-
</v-list>
50+
<v-table density="compact">
51+
<tbody>
52+
<tr>
53+
<td><strong>First Name</strong></td>
54+
<td>{{ user.firstName }}</td>
55+
</tr>
56+
<tr>
57+
<td><strong>Last Name</strong></td>
58+
<td>{{ user.lastName }}</td>
59+
</tr>
60+
<tr>
61+
<td><strong>Email</strong></td>
62+
<td>{{ user.email }}</td>
63+
</tr>
64+
<tr>
65+
<td><strong>Username</strong></td>
66+
<td>{{ user.username }}</td>
67+
</tr>
68+
<tr>
69+
<td><strong>Role</strong></td>
70+
<td>
71+
<v-chip class="ma-2" color="primary" label v-if="user.role">
72+
<v-icon icon="mdi-account-circle-outline" start></v-icon>
73+
{{ user.role.name }}
74+
</v-chip>
75+
<span v-else>-</span>
76+
</td>
77+
</tr>
78+
<tr>
79+
<td><strong>Groups</strong></td>
80+
<td>
81+
<v-chip v-for="group in user.userGroups" :key="group.id" class="ma-1" color="grey">{{ group.name }}</v-chip>
82+
<span v-if="!user.userGroups || user.userGroups.length === 0">-</span>
83+
</td>
84+
</tr>
85+
<tr>
86+
<td><strong>Provider</strong></td>
87+
<td>{{ user.provider || 'local' }}</td>
88+
</tr>
89+
</tbody>
90+
</v-table>
9191
</v-card>
9292
</v-col>
9393
</v-row>

client/src/layouts/default/NavDrawer.vue

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@
66
rail
77
>
88
<v-list>
9-
<v-list-item
10-
prepend-avatar="https://randomuser.me/api/portraits/women/85.jpg"
11-
subtitle="sandra_a88@gmailcom"
12-
title="Sandra Adams"
9+
<v-list-item
1310
link to="/profile"
14-
></v-list-item>
11+
>
12+
<template #prepend>
13+
<v-avatar size="30">
14+
<v-img :src="userAvatar || '/avatar.svg'" alt="User avatar" />
15+
</v-avatar>
16+
</template>
17+
<template #title>
18+
{{ userName }}
19+
</template>
20+
<template #subtitle>
21+
{{ userEmail }}
22+
</template>
23+
</v-list-item>
1524
</v-list>
1625

1726
<v-divider></v-divider>
@@ -199,9 +208,32 @@
199208
</template>
200209

201210
<script lang="ts" setup>
211+
import { ref, onMounted } from 'vue'
202212
import { useTheme } from 'vuetify'
213+
import axios from 'axios'
203214
const theme = useTheme()
204215
216+
const userAvatar = ref<string>('')
217+
const userName = ref<string>('')
218+
const userEmail = ref<string>('')
219+
220+
async function loadUserProfile() {
221+
try {
222+
const res = await axios.get('/api/users/profile')
223+
userName.value = `${res.data.firstName || ''} ${res.data.lastName || ''}`.trim() || res.data.username
224+
userEmail.value = res.data.email
225+
userAvatar.value = res.data.image
226+
} catch {
227+
userName.value = 'Profile'
228+
userEmail.value = ''
229+
userAvatar.value = '/avatar.svg'
230+
}
231+
}
232+
233+
onMounted(() => {
234+
loadUserProfile()
235+
})
236+
205237
function toggleTheme() {
206238
theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
207239
localStorage.setItem("theme", theme.global.name.value);

server/src/users/users.controller.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
UseGuards,
1313
UploadedFile,
1414
UseInterceptors,
15+
Response,
1516

1617
} from '@nestjs/common';
1718
import {
@@ -25,7 +26,7 @@ import { OKDTO } from '../common/dto/ok.dto';
2526
import { User, UsersService } from './users.service';
2627
import { GetAllUsersDTO } from './dto/users.dto';
2728
import { FileInterceptor } from '@nestjs/platform-express';
28-
import { Express } from 'express';
29+
import { Response as ResType } from 'express';
2930

3031
@Controller({ path: 'api/users', version: '1' })
3132
export class UsersController {
@@ -278,4 +279,39 @@ export class UsersController {
278279
}
279280
return this.usersService.updateAvatar(user.userId, file);
280281
}
282+
/*
283+
@Get('/profile/avatar')
284+
@UseGuards(JwtAuthGuard)
285+
@ApiBearerAuth('bearerAuth')
286+
@ApiForbiddenResponse({
287+
description: 'Error: Unauthorized',
288+
type: OKDTO,
289+
isArray: false,
290+
})
291+
@ApiOkResponse({
292+
description: 'Get current User avatar',
293+
type: GetAllUsersDTO,
294+
isArray: false,
295+
})
296+
@ApiOperation({ summary: 'Get current User avatar' })
297+
async getProfileAvatar(@Request() req: any, @Response() res: ResType) {
298+
const user = req.user;
299+
const avatarImage = await this.usersService.getAvatar(user.userId);
300+
301+
if (!avatarImage) {
302+
throw new HttpException('No avatar image found', HttpStatus.NOT_FOUND);
303+
}
304+
305+
// Parse data URL: data:[<mediatype>][;base64],<data>
306+
const matches = avatarImage.match(/^data:(.+);base64,(.+)$/);
307+
if (!matches || matches.length !== 3) {
308+
throw new HttpException('Invalid avatar image format', HttpStatus.INTERNAL_SERVER_ERROR);
309+
}
310+
const contentType = matches[1];
311+
const imageBuffer = Buffer.from(matches[2], 'base64');
312+
res.setHeader('Content-Type', contentType);
313+
res.setHeader('Content-Length', imageBuffer.length);
314+
return res.end(imageBuffer);
315+
}
316+
*/
281317
}

server/src/users/users.service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,12 @@ export class UsersService {
272272
}
273273
}
274274

275+
async getAvatar(userId: string): Promise<string | null> {
276+
const user = await this.prisma.user.findUnique({
277+
where: { id: userId },
278+
select: { image: true },
279+
});
280+
return user ? user.image : null;
281+
}
282+
275283
}

0 commit comments

Comments
 (0)