Skip to content

Feature/speaker profile bio #258

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

Merged
Merged
Show file tree
Hide file tree
Changes from 6 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
34 changes: 34 additions & 0 deletions packages/frontendmu-data/data/speakers-profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[
{
"bio": "Front-end Developer based in Mauritius.",
"job_title": "Front-end",
"location": "Mauritius",
"website": "https://sandeep.ramgolam.com",
"github": "MrSunshyne",
"twitter": "@__sun__"
},
{
"bio": "",
"job_title": "",
"location": "Mauritius",
"website": "",
"github": "saamiyah",
"twitter": ""
},
{
"bio": "",
"job_title": "",
"location": "Mauritius",
"website": "",
"github": "kushul",
"twitter": ""
},
{
"bio": "",
"job_title": "",
"location": "Mauritius",
"website": "",
"github": "cedpoilly",
"twitter": ""
}
]
103 changes: 80 additions & 23 deletions packages/frontendmu-nuxt/components/speaker/Single.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,42 @@ import LogoSpiral from '@/components/misc/LogoSpiral.vue'
import { getGithubUrl } from '@/utils/helpers'
import ContentBlock from '@/components/misc/ContentBlock.vue'

const props = defineProps({
speaker: {
type: Object,
required: true,
},
})

const speaker_photo = getGithubUrl(props.speaker.person?.github_account)
import type { SpeakerProfileWithSessions } from '~/utils/types'

interface SpeakerSingleProps {
speaker: SpeakerProfileWithSessions
}

const props = defineProps<SpeakerSingleProps>()

const person = computed(() => props.speaker.person)
const sessions = computed(() => props.speaker.sessions)

const profile = computed(() => props.speaker.profile)
const hasProfileBio = computed(() => profile.value.bio !== '')
const hasProfileLocation = computed(() => profile.value.location !== '')
const hasProfileWebsite = computed(() => profile.value.website !== '')
const hasProfileGithub = computed(() => profile.value.github !== '')
const hasProfileTwitter = computed(() => profile.value.twitter !== '')

const speaker_photo = getGithubUrl(person.value.github_account)
</script>

<template>
<div>
<!-- <pre>
<code>{{ JSON.stringify(props.speaker, null, 2) }}</code>
</pre> -->
<div :data-title="props.speaker.person?.name">
<div :data-title="person.name">
<ContentBlock>
<div class="flex md:flex-row justify-between flex-col-reverse">
<div>
<div class="flex flex-col-reverse md:flex-row justify-start md:gap-6">
<div class="flex-grow">
<!-- Content area -->
<div>
<div>
<div class="hidden md:block">
<BaseHeading :level="1" weight="bold">
{{ props.speaker.person?.name }}
{{ person.name }}
</BaseHeading>
</div>

<EventsList :sessions="props.speaker.sessions" />
<EventsList :sessions="sessions" />
</div>

<!-- Stats section -->
Expand Down Expand Up @@ -61,13 +69,62 @@ const speaker_photo = getGithubUrl(props.speaker.person?.github_account)
</div>
</div>

<div class="flex-grow relative">
<div class="w-full flex justify-end">
<img class="h-auto w-[80%] mx-auto md:mx-0 my-10 object-cover rounded-full lg:h-96 lg:w-96"
:src="speaker_photo" :style="vTransitionName(props.speaker.person?.name, 'photo')"
:alt="props.speaker.person?.name" :title="props.speaker.person?.name" width="300" height="300"
<div class="flex-grow relative w-full lg:max-w-[32.375rem]">
<div class="flex flex-col justify-start items-end w-full ">
<img
class="h-auto w-[80%] mx-auto md:mx-0 my-10 object-cover rounded-full lg:h-96 lg:w-96"
:src="speaker_photo" :style="vTransitionName(person.name, 'photo')"
:alt="person.name" :title="person.name" width="300" height="300"
>

<div
v-if="profile"
class="grid gap-4 w-full p-4 border-2 border-verse-400 rounded-xl z-20 text-verse-600 dark:text-verse-300"
>
<div class="w-full h-full absolute top-0">
<BaseHeading
class="md:hidden"
:level="1"
weight="bold"
>
{{ person.name }}
</BaseHeading>

<p v-if="hasProfileBio">
{{ profile.bio }}
</p>

<nav class="grid gap-2 *:flex *:justify-start *:items-center *:gap-2">
<span v-if="hasProfileLocation">
<Icon name="lucide:map-pin" mode="svg" class="size-6" />{{ profile.location }}
</span>

<NuxtLink
v-if="hasProfileWebsite"
:to="profile.website"
target="_blank"
>
<Icon name="lucide:link" mode="svg" class="size-6" />{{ profile.website }}
</NuxtLink>

<NuxtLink
v-if="hasProfileGithub"
:to="`https://github.com/${profile.github}`"
target="_blank"
>
<Icon name="lucide:github" mode="svg" class="size-6" />{{ profile.github }}
</NuxtLink>

<NuxtLink
v-if="hasProfileTwitter"
:to="`https://twitter.com/${profile.twitter}`"
target="_blank"
>
<Icon name="ri:twitter-x-fill" mode="svg" class="size-6" />{{ profile.twitter }}
</NuxtLink>
</nav>
</div>

<div class="w-full h-full absolute top-0 z-10">
<LogoSpiral class="w-full opacity-5 saturate-0" />
</div>
</div>
Expand Down
34 changes: 34 additions & 0 deletions packages/frontendmu-nuxt/error.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script setup lang="ts">
import type { NuxtError } from '#app'

const props = defineProps({
error: Object as () => NuxtError,
})

const handleError = () => clearError({ redirect: '/' })
</script>

<template>
<NuxtLayout name="simple">
<ContentBlock>
<div class="flex flex-col justify-start items-center gap-10 mx-auto max-w-[60ch] text-center">
<BaseHeading :level="1" weight="bold">
Oops!
</BaseHeading>

<BaseHeading :level="3" weight="bold">
{{ error.statusCode }} - The page you are looking for doesn't exist
</BaseHeading>

<p>{{ error.message }}</p>

<button
class="mx-auto bg-verse-500 hover:bg-verse-600 transition-colors duration-200 text-md block rounded-full px-4 py-4 text-center font-medium text-white md:px-6 md:text-lg cursor-pointer"
@click="handleError"
>
Back to Home
</button>
</div>
</ContentBlock>
</NuxtLayout>
</template>
51 changes: 40 additions & 11 deletions packages/frontendmu-nuxt/pages/speaker/[id].vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
<script setup lang="ts">
import eventsResponse from '../../../frontendmu-data/data/meetups-raw.json'
import speakersResponse from '../../../frontendmu-data/data/speakers-raw.json'
import speakersProfileResponse from '../../../frontendmu-data/data/speakers-profile.json'

import type { SpeakerProfileWithSessions } from '~/utils/types'

definePageMeta({
middleware: [
function (to, _) {
const { id } = to.params
const speaker = speakersResponse.find((ev: { id: string }) => String(ev.id) === String(id))

if (!speaker) {
return abortNavigation(
createError({
status: 404,
message: `We could not find the speaker with ID: ${id}`,
}),
)
}
},
],
})

const route = useRoute()
const id = computed(() => route.params.id as string)

function getSpeaker(id: string | number) {
function getSpeaker(id: string): SpeakerProfileWithSessions {
const speaker = speakersResponse.find((ev: { id: string }) => String(ev.id) === String(id))

if (speaker === null) {
console.error('cannot find speaker id: ', id)
if (!speaker) {
return {
person: undefined,
sessions: undefined,
profile: undefined,
Date: '',
Venue: '',
}
}

// Get sessions of this speaker from the events
Expand All @@ -19,18 +45,21 @@ function getSpeaker(id: string | number) {
return id === session_speaker_id
})

const profile = speakersProfileResponse.find(profile => profile.github === speaker.github_account)

return {
person: speaker,
sessions: speakerSession,
profile,
Date: '',
Venue: '',
}
}

const speaker = ref(getSpeaker(id.value))

const speakerExists = computed(() => speaker.value !== null)
const speaker = ref(getSpeaker(route.params.id as string))

useHead({
title: speaker.value.person ? speaker.value.person.name : '',
title: speaker.value?.person ? speaker.value.person.name : '',
meta: [
{
hid: 'description',
Expand All @@ -41,15 +70,15 @@ useHead({
})

defineOgImageComponent('Speaker', {
title: speaker.value.person ? speaker.value.person.name : '',
username: speaker.value.person?.github_account,
title: speaker.value?.person ? speaker.value.person.name : '',
username: speaker.value?.person.github_account,
})
</script>

<template>
<div>
<template v-if="speaker">
<SpeakerSingle :route-id="id" :speaker="speaker" />
<SpeakerSingle :speaker="speaker" />
</template>
</div>
</template>
25 changes: 24 additions & 1 deletion packages/frontendmu-nuxt/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,14 @@ export interface SessionDetail {
}

export interface Speaker {
name: string
id: string
status: string
sort: string | null
name: string
github_account: string
featured: boolean
date_created: string
date_updated: string
}

export interface BrandingAsset {
Expand All @@ -269,3 +274,21 @@ export interface BrandingAsset {
filename: string
versions: string[]
}

export interface SpeakerProfile {
id: string
bio: string
job_title: string
location: string
website: string
github: string
twitter: string
}

export interface SpeakerProfileWithSessions {
person: Speaker
sessions: Sponsor[]
profile: SpeakerProfile
Date: string
Venue: string
}