Skip to content

Commit dfa1258

Browse files
committed
feat: enhance user profile functionality with social links and error handling
1 parent 9fea0c8 commit dfa1258

File tree

6 files changed

+45
-28
lines changed

6 files changed

+45
-28
lines changed

server/src/song/song.service.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export class SongService {
187187
}
188188

189189
public async getSongByPage(query: PageQueryDTO): Promise<SongPreviewDto[]> {
190-
const { page, limit, sort, order, user } = query;
190+
const { page, limit, sort, order } = query;
191191

192192
if (!page || !limit || !sort) {
193193
throw new HttpException(
@@ -196,8 +196,10 @@ export class SongService {
196196
);
197197
}
198198

199-
let filter = {};
199+
const filter = {};
200200

201+
/*
202+
// TODO: Decide if user filtering is necessary
201203
if (user) {
202204
const userDocument = await this.userService.findByUsername(user);
203205
@@ -209,6 +211,7 @@ export class SongService {
209211
uploader: userDocument._id,
210212
};
211213
}
214+
*/
212215

213216
const songs = (await this.songModel
214217
.find({

server/src/user/entity/user.entity.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
22
import { HydratedDocument, Schema as MongooseSchema, Types } from 'mongoose';
33

44
@Schema({})
5-
class SocialLinks {
5+
export class SocialLinks {
66
bandcamp?: string;
77
discord?: string;
88
facebook?: string;

server/src/user/user.controller.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import {
33
Controller,
44
Get,
55
Inject,
6+
NotFoundException,
67
Param,
78
Patch,
89
Query,
910
} from '@nestjs/common';
1011
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
1112
import { PageQueryDTO } from '@shared/validation/common/dto/PageQuery.dto';
1213
import { UpdateUserProfileDto } from '@shared/validation/user/dto/UpdateUserProfile.dto';
14+
import { UserProfileViewDto } from '@shared/validation/user/dto/UserProfileView.dto';
1315
import { UserQuery } from '@shared/validation/user/dto/UserQuery.dto';
1416

1517
import { GetRequestToken, validateUser } from '@server/lib/GetRequestUser';
@@ -44,8 +46,16 @@ export class UserController {
4446
@Get(':username')
4547
@ApiTags('user')
4648
@ApiOperation({ summary: 'Get user profile by username' })
47-
async getUserProfile(@Param('username') username: string) {
48-
return await this.userService.findByUsername(username);
49+
async getUserProfile(
50+
@Param('username') username: string,
51+
): Promise<UserProfileViewDto> {
52+
const doc = await this.userService.findByUsername(username);
53+
54+
if (!doc) {
55+
throw new NotFoundException('User not found');
56+
}
57+
58+
return UserProfileViewDto.fromUserDocument(doc);
4959
}
5060

5161
@Patch()

shared/validation/user/dto/UserProfileView.dto.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { UserDocument } from '@server/user/entity/user.entity';
1+
import { SocialLinks, UserDocument } from '@server/user/entity/user.entity';
22

33
export class UserProfileViewDto {
44
username: string;
@@ -9,7 +9,8 @@ export class UserProfileViewDto {
99
loginCount: number;
1010
loginStreak: number;
1111
playCount: number;
12-
// socialLinks: Record<keyof typeof UserLinks, string | undefined>;
12+
13+
socialLinks: InstanceType<typeof SocialLinks>;
1314

1415
public static fromUserDocument(user: UserDocument): UserProfileViewDto {
1516
return new UserProfileViewDto({
@@ -21,7 +22,7 @@ export class UserProfileViewDto {
2122
loginCount: user.loginCount,
2223
loginStreak: user.loginStreak,
2324
playCount: user.playCount,
24-
// socialLinks: user.socialLinks,
25+
socialLinks: user.socialLinks,
2526
});
2627
}
2728

web/src/app/(content)/user/[username]/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ErrorBox } from '@web/src/modules/shared/components/client/ErrorBox';
2-
import UserProfile from '@web/src/modules/user/components/UserProfile';
2+
import { UserProfile } from '@web/src/modules/user/components/UserProfile';
33
import {
44
getUserProfileData,
55
getUserSongs,

web/src/modules/user/components/UserProfile.tsx

+22-19
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ type UserProfileProps = {
1313
songData: SongPreviewDto[] | null;
1414
};
1515

16-
const UserProfile = ({ userData, songData }: UserProfileProps) => {
17-
const { lastSeen, username, description, profileImage } = userData;
16+
export const UserProfile = ({ userData, songData }: UserProfileProps) => {
17+
const { lastSeen, username, description, profileImage, socialLinks } =
18+
userData;
1819

1920
return (
2021
<div className='max-w-screen-lg mx-auto'>
@@ -23,7 +24,7 @@ const UserProfile = ({ userData, songData }: UserProfileProps) => {
2324
<div className='flex items-center gap-8'>
2425
<Image
2526
src={profileImage}
26-
alt={username}
27+
alt={`Profile picture of ${username}`}
2728
className='w-32 h-32 rounded-full'
2829
width={128}
2930
height={128}
@@ -38,29 +39,30 @@ const UserProfile = ({ userData, songData }: UserProfileProps) => {
3839
{/* Username/handle */}
3940
<p className='text-zinc-400 my-1'>
4041
<span className='font-black text-zinc-200'>{`@${username}`}</span>
41-
{` • 5 songs • 2,534 plays`}
42+
{` • ${songData?.length || 0} songs • 2,534 plays`}{' '}
43+
{/* Dynamic song count */}
4244
</p>
4345

4446
{/* Description */}
4547
<p className='text-zinc-400 my-1 line-clamp-3'>
46-
Hello! This is my user description.
48+
{description || 'No description available.'}{' '}
49+
{/* Dynamic description */}
4750
</p>
4851

4952
{/* Social links */}
5053
<div className='flex-grow flex flex-row gap-1.5 mt-4'>
51-
<UserSocialIcon icon='twitter' href='#' />
52-
<UserSocialIcon icon='youtube' href='#' />
53-
<UserSocialIcon icon='github' href='#' />
54-
<UserSocialIcon icon='discord' href='#' />
55-
<UserSocialIcon icon='patreon' href='#' />
54+
{Object.entries(socialLinks).map(([key, value], i) => (
55+
<UserSocialIcon key={i} icon={key} href={value} />
56+
))}
5657
</div>
5758
</div>
59+
5860
<div className='flex-grow'></div>
61+
5962
<div>
6063
{/* Joined */}
6164
<p className='text-zinc-500'>Joined</p>
6265
<p className='font-bold text-zinc-400 mb-4'>
63-
{/* TODO: lastSeen is supposed to be a date, but it's a string */}
6466
{new Date(lastSeen).toLocaleDateString('en-UK')}
6567
<span className='font-normal text-zinc-400'>{` (${formatTimeAgo(
6668
new Date(lastSeen),
@@ -70,7 +72,6 @@ const UserProfile = ({ userData, songData }: UserProfileProps) => {
7072
{/* Last seen */}
7173
<p className='text-zinc-500'>Last seen</p>
7274
<p className='font-bold text-zinc-400'>
73-
{/* TODO: lastSeen is supposed to be a date, but it's a string */}
7475
{new Date(lastSeen).toLocaleDateString('en-UK')}
7576
<span className='font-normal text-zinc-400'>{` (${formatTimeAgo(
7677
new Date(lastSeen),
@@ -85,14 +86,16 @@ const UserProfile = ({ userData, songData }: UserProfileProps) => {
8586
{/* UPLOADED SONGS */}
8687
<section>
8788
<h2 className='flex-1 text-xl uppercase mb-4 text-zinc-200'>Songs</h2>
88-
<SongCardGroup>
89-
{songData?.map((song, i) => (
90-
<SongCard key={i} song={song} />
91-
))}
92-
</SongCardGroup>
89+
{songData ? (
90+
<SongCardGroup>
91+
{songData.map((song, i) => (
92+
<SongCard key={i} song={song} />
93+
))}
94+
</SongCardGroup>
95+
) : (
96+
<p className='text-zinc-400'>No songs uploaded yet.</p>
97+
)}
9398
</section>
9499
</div>
95100
);
96101
};
97-
98-
export default UserProfile;

0 commit comments

Comments
 (0)