Skip to content

Commit

Permalink
Merge pull request #22 from fiit-tp7-2023/AB#350-user-profile-page
Browse files Browse the repository at this point in the history
Ab#350 user profile page
  • Loading branch information
bran0h authored Apr 23, 2024
2 parents a961256 + e3738f9 commit 6ee1455
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 11 deletions.
139 changes: 133 additions & 6 deletions pages/profile/[address].vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,142 @@
<template>
<div>
<h1>User profile</h1>
<h2>{{ address }}</h2>
<div class="profile-container">
<h1 class="text-2xl font-mono text-pink-500">{{ userProfile.username }}</h1>
<div class="user-details mt-4 bg-slate-900 text-white p-6 rounded-lg flex">
<img
:src="userProfile.profilePicture || 'default-profile.png'"
alt="Profile Picture"
class="profile-picture shadow-lg"
/>
<div class="info ml-6">
<h2 class="text-xl font-mono">{{ userProfile.address }}</h2>
<div class="follow-info mt-4">
<h3>Followers: {{ userProfile.followerCount }}</h3>
<h3>Following: {{ userProfile.followingCount }}</h3>
</div>
<button v-if="!userProfile.isOwnProfile" class="follow-button" @click="followUser">Follow</button>
</div>
</div>
<hr class="mt-6 border-t border-gray-500" />
</div>
</template>

<script lang="ts" setup>
import { computed } from 'vue';
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useAccountStore } from '~/store';
const error = ref<Error | null>(null);
const route = useRoute();
const address = computed(() => route.params.address);
const accountStore = useAccountStore();
const userProfile = ref({
username: '',
address: '',
profilePicture: '',
followerCount: 0,
followingCount: 0,
isOwnProfile: false,
});
const fetchUserProfile = async () => {
const address = route.params.address; // Get the address from the URL parameter
try {
const data = await $fetch(`/api/user/${address}`, {
headers: {
Authorization: `Bearer ${accountStore.accessToken}`,
},
});
userProfile.value = {
...data,
username: data.username || 'Anonymous', // Default if undefined
profilePicture: data.profilePicture || 'default-profile.png',
isOwnProfile: accountStore.address === address,
followerCount: data.followerCount || 0, // Default to 0 if undefined
followingCount: data.followingCount || 0,
};
} catch (e) {
error.value = e as Error;
}
};
const followUser = async () => {
await $fetch(`/api/user/:id/following`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accountStore.accessToken}`,
},
});
fetchUserProfile(); // Re-fetch or adjust local state to show updated follower count
};
onMounted(fetchUserProfile);
</script>

<style></style>
<style scoped>
.profile-container {
@apply max-w-5xl mx-auto p-5 shadow-lg rounded-lg bg-[#1e293b];
}
.user-details {
@apply flex items-center justify-between bg-[#1e293b] rounded-lg p-5;
}
.profile-picture {
@apply w-36 h-36 rounded-full object-cover border-4 border-white shadow-lg;
}
.info {
@apply flex flex-col justify-center ml-5 grow;
}
h1,
h2,
h3 {
@apply text-white;
}
h1 {
@apply text-2xl font-mono text-[#f472b6];
}
h2 {
@apply text-xl font-mono;
}
.follow-button {
@apply mt-5 py-2.5 px-5 bg-[#f472b6] text-white border-none rounded cursor-pointer transition-colors;
width: 100%; /* Ensures the button stretches to fill its container */
}
.follow-button:hover {
@apply bg-[#ec4899];
}
.follow-info {
@apply flex gap-5 mt-2.5 justify-end items-center;
width: 100%; /* This ensures the flex container takes full width */
}
.follow-info h3 {
@apply bg-[rgba(255,255,255,0.1)] py-2.5 px-2.5 rounded flex-1 text-center; /* flex-1 allows each item to take equal space */
}
hr {
@apply mt-5 border-t border-[rgba(130,130,130,0.5)];
}
@media (max-width: 768px) {
.user-details {
@apply flex-col items-center;
}
.info {
@apply ml-0 mt-5;
}
.follow-info {
@apply justify-center;
}
}
</style>
117 changes: 114 additions & 3 deletions pages/profile/index.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,118 @@
<template>
<div>Your profile</div>
<div class="profile-container">
<h1 class="text-2xl font-mono text-pink-500">{{ userProfile.username }}</h1>
<div class="user-details mt-4 bg-slate-900 text-white p-6 rounded-lg flex">
<img :src="userProfile.profilePicture" alt="Profile Picture" class="profile-picture shadow-lg" />
<div class="info ml-6">
<h2 class="text-xl font-mono">{{ userProfile.address }}</h2>
<div class="follow-info mt-4">
<h3>Followers: {{ userProfile.followerCount }}</h3>
<h3>Following: {{ userProfile.followingCount }}</h3>
</div>
</div>
</div>
<hr class="mt-6 border-t border-gray-500" />
</div>
</template>

<script lang="ts" setup></script>
<script lang="ts" setup>
import { useAccountStore } from '~/store';
<style></style>
const accountStore = useAccountStore();
const error = ref<Error | null>(null);
const userProfile = ref({
username: '',
address: '',
profilePicture: '',
followerCount: 0,
followingCount: 0,
isOwnProfile: false,
});
const fetchUserProfile = async () => {
try {
const data = await $fetch(`/api/user/${accountStore.address}`, {
headers: {
Authorization: `Bearer ${accountStore.accessToken}`,
},
});
userProfile.value = {
...data,
username: data.username || 'Anonymous',
profilePicture: data.profilePicture || '',
isOwnProfile: true,
followerCount: data.followerCount || 0,
followingCount: data.followingCount || 0,
};
} catch (e) {
error.value = e as Error;
}
};
onMounted(fetchUserProfile);
</script>

<style scoped>
.profile-container {
@apply max-w-5xl mx-auto p-5 shadow-lg rounded-lg bg-[#1e293b];
}
.user-details {
@apply flex items-center bg-[#1e293b] rounded-lg p-5;
}
.profile-picture {
@apply w-36 h-36 rounded-full object-cover border-4 border-white shadow-lg;
}
.info {
@apply flex flex-col justify-center ml-5;
}
h1,
h2,
h3 {
@apply text-white;
}
h1 {
@apply text-2xl font-mono text-[#f472b6];
}
h2 {
@apply text-xl font-mono;
}
.follow-button {
@apply mt-5 py-2.5 px-5 bg-[#f472b6] text-white border-none rounded cursor-pointer transition-colors;
}
.follow-button:hover {
@apply bg-[#ec4899];
}
.follow-info {
@apply flex gap-5 mt-2.5 justify-end;
}
.follow-info h3 {
@apply bg-[rgba(255,255,255,0.1)] py-2.5 px-2.5 rounded;
}
hr {
@apply mt-5 border-t border-[rgba(130,130,130,0.5)];
}
@media (max-width: 768px) {
.user-details {
@apply flex-col items-center;
}
.info {
@apply ml-0 mt-5;
}
}
</style>
18 changes: 18 additions & 0 deletions server/api/user/[id]/followers/index.delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useUserService } from '~/server/services/user.service';

export default defineEventHandler(async (event) => {
const jwt = getHeader(event, 'Authorization')?.split('Bearer ')[1];
const nftAddress = getRouterParam(event, 'id');
if (!jwt) {
throw createError({
message: 'Unauthorized',
});
}
if (!nftAddress) {
throw createError({
message: 'Invalid NFT address',
});
}
const service = useUserService(jwt);
return await service.deleteFollowers(nftAddress);
});
18 changes: 18 additions & 0 deletions server/api/user/[id]/followers/index.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useUserService } from '~/server/services/user.service';

export default defineEventHandler(async (event) => {
const jwt = getHeader(event, 'Authorization')?.split('Bearer ')[1];
const nftAddress = getRouterParam(event, 'id');
if (!jwt) {
throw createError({
message: 'Unauthorized',
});
}
if (!nftAddress) {
throw createError({
message: 'Invalid NFT address',
});
}
const service = useUserService(jwt);
return await service.getFollowers(nftAddress);
});
18 changes: 18 additions & 0 deletions server/api/user/[id]/following/index.delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useUserService } from '~/server/services/user.service';

export default defineEventHandler(async (event) => {
const jwt = getHeader(event, 'Authorization')?.split('Bearer ')[1];
const nftAddress = getRouterParam(event, 'id');
if (!jwt) {
throw createError({
message: 'Unauthorized',
});
}
if (!nftAddress) {
throw createError({
message: 'Invalid NFT address',
});
}
const service = useUserService(jwt);
return await service.deleteFollow(nftAddress);
});
18 changes: 18 additions & 0 deletions server/api/user/[id]/following/index.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useUserService } from '~/server/services/user.service';

export default defineEventHandler(async (event) => {
const jwt = getHeader(event, 'Authorization')?.split('Bearer ')[1];
const nftAddress = getRouterParam(event, 'id');
if (!jwt) {
throw createError({
message: 'Unauthorized',
});
}
if (!nftAddress) {
throw createError({
message: 'Invalid NFT address',
});
}
const service = useUserService(jwt);
return await service.getFollowing(nftAddress);
});
18 changes: 18 additions & 0 deletions server/api/user/[id]/following/index.post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useUserService } from '~/server/services/user.service';

export default defineEventHandler(async (event) => {
const jwt = getHeader(event, 'Authorization')?.split('Bearer ')[1];
const nftAddress = getRouterParam(event, 'id');
if (!jwt) {
throw createError({
message: 'Unauthorized',
});
}
if (!nftAddress) {
throw createError({
message: 'Invalid NFT address',
});
}
const service = useUserService(jwt);
return await service.follow(nftAddress);
});
18 changes: 18 additions & 0 deletions server/api/user/[id]/index.get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useUserService } from '~/server/services/user.service';

export default defineEventHandler(async (event) => {
const jwt = getHeader(event, 'Authorization')?.split('Bearer ')[1];
const nftAddress = getRouterParam(event, 'id');
if (!jwt) {
throw createError({
message: 'Unauthorized',
});
}
if (!nftAddress) {
throw createError({
message: 'Invalid NFT address',
});
}
const service = useUserService(jwt);
return await service.getUserProfile(nftAddress);
});
Loading

0 comments on commit 6ee1455

Please sign in to comment.