Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 154 create profiles page #188

Merged
merged 17 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions client/src/components/ChallengeInteraction.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ onUnmounted(() => {

defineProps<{
title: string
description: string
description?: string
type: 'Connect' | 'Understand' | 'Act'
points: number
startDate: string
finishDate: string
finishDate?: string
status: 'Complete' | 'In Progress'
}>()
</script>
<template>
<v-card class="pa-4 bg-primaryBrown challenge-row" outlined>
<v-card class="pa-4 mb-2 bg-primaryBrown challenge-row" outlined>
<v-card-title class="d-flex justify-space-between align-center">
<div>
<span class="font-weight-bold text-h6">{{ title }}</span>
Expand All @@ -42,7 +42,7 @@ defineProps<{
<div class="d-flex flex-column">
<v-card-text class="d-flex justify-space-between align-center">
<div class="details">
<p>
<p v-if="description && status === 'Complete'" class="description">
{{ description }}
</p>
</div>
Expand All @@ -51,7 +51,10 @@ defineProps<{
<div class="d-flex flex-row align-center justify-space-between ml-5 mr-5">
<div>
<p class="mb-2"><strong>Start Date:</strong> {{ startDate }}</p>
<p><strong>Finish Date:</strong> {{ finishDate }}</p>
<p v-if="status === 'Complete'">
<strong>Finish Date:</strong>
{{ finishDate }}
</p>
</div>
<div class="align-center justify-center">
<v-chip
Expand Down
23 changes: 9 additions & 14 deletions client/src/router/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { useModalStore } from '@/stores/modal'
import type { RouteLocationNormalized } from 'vue-router'

import LandingView from '@/views/LandingView.vue'
import LeaderboardView from '@/views/LeaderboardView.vue'
Expand All @@ -17,6 +18,14 @@ const router = createRouter({
name: 'landing',
component: LandingView,
},
{
path: '/profile/:username?',
name: 'user-profile',
component: ProfileView,
props: (route: RouteLocationNormalized) => ({
username: route.params.username as string | undefined,
}),
},
{
path: '/friends',
name: 'friends',
Expand Down Expand Up @@ -48,20 +57,6 @@ const router = createRouter({
path: '/:pathMatch(.*)*',
redirect: '/404',
},
{
path: '/404',
name: '404',
component: () => import('@/views/404Error.vue'),
},
{
path: '/:pathMatch(.*)*',
redirect: '/404',
},
{
path: '/profile',
name: 'profile',
component: ProfileView,
},
],
})

Expand Down
160 changes: 160 additions & 0 deletions client/src/utils/mockdata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import type { User } from '@/types/user'

interface Challenge {
title: string
description: string
type: 'Connect' | 'Understand' | 'Act'
points: number
startDate: string
finishDate?: string
status: 'Complete' | 'In Progress'
}

interface MockUser extends User {
challenges: Challenge[]
}

export const mockUsers: Record<string, MockUser> = {
johndoe: {
userId: 1,
userName: 'johndoe',
firstName: 'John',
lastName: 'Doe',
bio: 'Environmental enthusiast passionate about ocean conservation. 🌊',
totalPoints: 3450,
email: '[email protected]',
visibility: 1,
avatar: 1,
isSuperuser: false,
challenges: [
{
title: 'Beach Clean-up Champion',
description: 'Lead a team of volunteers in cleaning up Ocean Beach',
type: 'Act',
points: 500,
startDate: '27/11/2024 10:27pm',
finishDate: '27/11/2024 11:27pm',
status: 'Complete',
},
{
title: 'Marine Life Documentation',
description: 'Photograph and identify local marine species',
type: 'Connect',
points: 300,
startDate: '28/11/2024 09:00am',
finishDate: '28/11/2024 11:00am',
status: 'Complete',
},
{
title: 'Ocean Conservation Workshop',
description: 'Participate in marine ecosystem workshop',
type: 'Understand',
points: 400,
startDate: '29/11/2024 02:00pm',
status: 'In Progress',
},
],
},
marinebiologist: {
userId: 2,
userName: 'marinebiologist',
firstName: 'Emma',
lastName: 'Wilson',
bio: 'Marine Biology PhD Student | Coral Reef Research 🐠',
totalPoints: 4200,
email: '[email protected]',
visibility: 2,
avatar: 2,
isSuperuser: false,
challenges: [
{
title: 'Coral Health Assessment',
description: 'Conduct detailed survey of coral reef health',
type: 'Connect',
points: 600,
startDate: '25/11/2024 08:00am',
finishDate: '25/11/2024 02:00pm',
status: 'Complete',
},
{
title: 'Public Education Session',
description: 'Host educational session about marine conservation',
type: 'Act',
points: 400,
startDate: '26/11/2024 03:00pm',
finishDate: '26/11/2024 05:00pm',
status: 'Complete',
},
{
title: 'Research Data Analysis',
description: 'Analyze collected marine ecosystem data',
type: 'Understand',
points: 500,
startDate: '29/11/2024 10:00am',
status: 'In Progress',
},
],
},
}

export const defaultUserData: MockUser = {
userId: 0,
userName: 'Username',
firstName: 'Firstname',
lastName: 'Lastname',
bio: 'This is a bio.',
totalPoints: 0,
email: '',
visibility: 1,
avatar: 0,
isSuperuser: false,
challenges: [],
}
export const currentUserData: MockUser = {
userId: 999,
userName: 'currentuser',
firstName: 'Current',
lastName: 'User',
bio: 'Welcome to my profile! No challenges completed yet.',
totalPoints: 0,
email: '[email protected]',
visibility: 1,
avatar: 0,
isSuperuser: false,
challenges: [
{
title: 'Ocean Explorer Initiation',
description: 'Complete your first beach cleanup activity',
type: 'Act',
points: 200,
startDate: '25/11/2024 09:00am',
finishDate: '25/11/2024 11:00am',
status: 'Complete',
},
{
title: 'Marine Life Study',
description: 'Document and identify 5 different marine species',
type: 'Connect',
points: 300,
startDate: '26/11/2024 02:00pm',
finishDate: '26/11/2024 04:30pm',
status: 'Complete',
},
{
title: 'Coastal Conservation Quiz',
description: 'Test your knowledge about coastal ecosystems',
type: 'Understand',
points: 150,
startDate: '27/11/2024 03:00pm',
status: 'In Progress',
},
{
title: 'Community Outreach',
description: 'Share ocean conservation tips with community members',
type: 'Connect',
points: 200,
startDate: '28/11/2024 01:00pm',
status: 'In Progress',
},
],
}
144 changes: 124 additions & 20 deletions client/src/views/ProfileView.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,130 @@
<script setup lang="ts">
import { ref } from 'vue'
import ChallengesCard from '@/components/ChallengeInteraction.vue'

const title = ref('Challenge 1')
const description = ref('This is a challenge')
const type = ref<'Connect' | 'Understand' | 'Act'>('Connect')
const points = ref(200)
const startDate = ref('27/11/2024 9:27pm')
const finishDate = ref('27/11/2024 10:27pm')
const status = ref<'Complete' | 'In Progress'>('Complete')
import { ref, onMounted } from 'vue'
import { useDisplay } from 'vuetify'
import { useUserStore } from '@/stores/user'
import { useModalStore } from '@/stores/modal'
import { useRouter } from 'vue-router'
import ChallengeInteraction from '@/components/ChallengeInteraction.vue'
import WaveBanner from '@/components/WaveBanner.vue'
import avatarPaths from '@/utils/avatar'
import { mockUsers, currentUserData } from '@/utils/mockdata' //for testing
import type { User } from '@/types/user'

// types
interface Challenge {
title: string
description: string
type: 'Connect' | 'Understand' | 'Act'
points: number
startDate: string
finishDate?: string
status: 'Complete' | 'In Progress'
}

interface UserWithChallenges extends User {
challenges: Challenge[]
}

const props = defineProps<{
username?: string
}>()

const { xs } = useDisplay()
const userStore = useUserStore()
const modalStore = useModalStore()
const router = useRouter()
const profileData = ref<UserWithChallenges | null>(null) //data for the profile
userStore.userData = currentUserData //for testing

const initializeProfile = () => {
if (!userStore.isLoggedIn) {
modalStore.openLogin()
router.push('/')
return
} //if user is not logged in, open login modal

if (props.username) {
const userData = mockUsers[props.username]
if (userData) {
profileData.value = userData
} else {
router.push('/404')
}
} else {
profileData.value = currentUserData
}
}

onMounted(() => {
initializeProfile()
})
</script>

<template>
<v-container>
<ChallengesCard
:title="title"
:description="description"
:type="type"
:points="points"
:startDate="startDate"
:finishDate="finishDate"
:status="status"
/>
<v-container v-if="profileData" fluid class="pa-0 d-flex flex-column">
<!-- Wave Banner Header -->
<v-row v-if="!xs" class="header">
<WaveBanner imageSrc="/teambuilding-background.jpg" />
<img src="/teambuilding-background.jpg" alt="Ocean Beach" class="header-image" />
</v-row>

<!-- Profile Content -->
<v-row class="px-4 px-sm-16 mx-0">
<v-col cols="12" class="d-flex flex-column">
<!-- Avatar and Basic Info -->
<div class="d-flex align-start mb-4">
<v-img
class="rounded-circle"
max-height="96"
max-width="96"
min-width="96"
contain
:src="avatarPaths[profileData.avatar]"
/>
</div>

<p class="text-h4 font-weight-bold mb-1">{{ profileData.userName }}</p>
<h3 class="text-h6 mb-1">{{ profileData.firstName }} {{ profileData.lastName }}</h3>
<p class="mb-1">{{ profileData.bio }}</p>
<p class="text-body-1">Total Points: {{ profileData.totalPoints }} pts</p>
</v-col>
</v-row>

<!-- Challenges Section -->
<v-row class="px-4 px-sm-16 mx-0">
<v-col cols="12">
<h2 class="text-h6 font-weight-bold mb-4">Challenges</h2>
<div class="d-flex flex-column gap-4">
<ChallengeInteraction
v-for="challenge in profileData.challenges"
:key="challenge.title"
v-bind="challenge"
/>
</div>
</v-col>
</v-row>
</v-container>
</template>

<style scoped>
.header {
width: 100%;
height: 200px;
position: relative;
overflow: hidden;
}

.header-image {
width: 100%;
height: 100%;
object-fit: cover;
}

h3 {
color: rgb(var(--v-theme-primaryGreen));
}

.gap-4 {
gap: 16px;
}
</style>