diff --git a/devcon-api/src/clients/recommendation.ts b/devcon-api/src/clients/recommendation.ts
index 70bb1b0a2..4112229c0 100644
--- a/devcon-api/src/clients/recommendation.ts
+++ b/devcon-api/src/clients/recommendation.ts
@@ -100,6 +100,10 @@ export async function GetRecommendedSessions(id: string, includeFeatured?: boole
},
],
},
+ include: {
+ speakers: true,
+ slot_room: true,
+ },
orderBy: {
slot_start: 'asc',
},
diff --git a/devcon-app/src/components/domain/app/dc7/dashboard/index.tsx b/devcon-app/src/components/domain/app/dc7/dashboard/index.tsx
index a8c005f8b..fcecf9e4c 100644
--- a/devcon-app/src/components/domain/app/dc7/dashboard/index.tsx
+++ b/devcon-app/src/components/domain/app/dc7/dashboard/index.tsx
@@ -10,7 +10,7 @@ import PassportLogoBlack from 'assets/images/dc-7/passport-logo-black.png'
import { NotificationCard } from 'components/domain/app/dc7/profile/notifications'
import { PersonalizedSuggestions } from 'components/domain/app/dc7/sessions/recommendations'
import { useRecoilState, useRecoilValue } from 'recoil'
-import { devaBotVisibleAtom, notificationsAtom, sessionsAtom, useSeenNotifications } from 'pages/_app'
+import { devaBotVisibleAtom, notificationsAtom, sessionsAtom, speakersAtom, useSeenNotifications } from 'pages/_app'
import FoodIcon from 'assets/icons/food-beverage.svg'
import CityGuideIcon from 'assets/icons/city-guide.svg'
import VideoIcon from 'assets/icons/video-play.svg'
@@ -25,6 +25,8 @@ import { Link } from 'components/common/link'
import { TruncateMiddle } from 'utils/formatting'
import ChevronRight from 'assets/icons/chevron_right.svg'
import { FancyLoader } from 'lib/components/loader/loader'
+import { RecommendedSpeakers } from '../speakers/recommendations'
+import { useSpeakerData } from 'services/event-data'
export const cardClass =
'flex flex-col lg:border lg:border-solid lg:border-[#E4E6EB] rounded-3xl relative lg:bg-[#fbfbfb]'
@@ -157,6 +159,7 @@ const featuredClass =
export const Dashboard = () => {
const accountContext = useAccountContext()
const sessions = useRecoilValue(sessionsAtom)
+ const speakers = useSpeakerData()
const draggableLink = useDraggableLink()
const [_, setDevaBotVisible] = useRecoilState(devaBotVisibleAtom)
const { account, loading } = accountContext
@@ -291,7 +294,13 @@ export const Dashboard = () => {
-
+
+
+
+
+
)
}
diff --git a/devcon-app/src/components/domain/app/dc7/sessions/recommendations.tsx b/devcon-app/src/components/domain/app/dc7/sessions/recommendations.tsx
index ae7bbb216..43bd0cb2e 100644
--- a/devcon-app/src/components/domain/app/dc7/sessions/recommendations.tsx
+++ b/devcon-app/src/components/domain/app/dc7/sessions/recommendations.tsx
@@ -8,6 +8,7 @@ import { useAccountContext } from 'context/account-context'
import { APP_CONFIG } from 'utils/config'
import { Separator } from 'lib/components/ui/separator'
import cn from 'classnames'
+import { FancyLoader } from 'lib/components/loader/loader'
interface Props {
sessions: SessionType[]
@@ -32,7 +33,7 @@ export function PersonalizedSuggestions({ sessions, standalone }: Props) {
[sessions, account]
)
- const { data: recommended } = useQuery({
+ const { data: recommended, isLoading } = useQuery({
queryKey: ['account', 'sessions', 'recommended', account?.id],
queryFn: async () => {
if (!account?.id) {
@@ -100,21 +101,16 @@ export function PersonalizedSuggestions({ sessions, standalone }: Props) {
name: 'Recommended',
list: recommended,
},
- ].map(({ name, list }) => {
- const isEmpty = !list?.length
+ ].map(({ name }) => {
return (
{
- if (!isEmpty) {
- setFilter(name.toLowerCase() as 'featured' | 'personal' | 'recommended')
- }
+ setFilter(name.toLowerCase() as 'featured' | 'personal' | 'recommended')
}}
>
{name}
@@ -126,6 +122,31 @@ export function PersonalizedSuggestions({ sessions, standalone }: Props) {
)}
+ {filter === 'personal' && !sessionList?.length && (
+
+
+
+ Complete your profile
+ {' '}
+ to see personalized recommendations.
+
+
+ )}
+ {filter === 'recommended' && isLoading && (
+
+
+
+ )}
+ {filter === 'recommended' && !isLoading && !sessionList?.length && (
+
+
+
+ Complete your profile
+ {' '}
+ to see personalized recommendations.
+
+
+ )}
{sessionList?.map((session: SessionType, index: number) => (
diff --git a/devcon-app/src/components/domain/app/dc7/speakers/index.tsx b/devcon-app/src/components/domain/app/dc7/speakers/index.tsx
index 2036f2ed1..6e05732b1 100644
--- a/devcon-app/src/components/domain/app/dc7/speakers/index.tsx
+++ b/devcon-app/src/components/domain/app/dc7/speakers/index.tsx
@@ -27,6 +27,7 @@ import { ScrollUpComponent } from '../sessions'
import { Popup } from 'lib/components/pop-up'
import { useAccountContext } from 'context/account-context'
import moment from 'moment'
+import { RecommendedSpeakers } from './recommendations'
// import { SessionFilterAdvanced } from '../sessions'
export const cardClass =
@@ -397,60 +398,33 @@ export const SpeakerList = ({ speakers }: { speakers: SpeakerType[] | null }) =>
setVisibleSpeakers(filteredSpeakers.slice(0, page * SPEAKERS_PER_PAGE))
}, [page, filteredSpeakers])
- return (
-
-
+ const onSpeakerSelect = (e: any, speaker: SpeakerType) => {
+ const result = draggableLink.onClick(e)
-
Featured Speakers
+ if (!result) return
-
-
-
- {featuredSpeakers.map((speaker, index) => (
-
{
- const result = draggableLink.onClick(e)
+ if (pathname === '/speakers' && isLargeScreen) e.preventDefault()
- if (!result) return
+ if (isLargeScreen) {
+ if (selectedSpeaker?.sourceId === speaker.sourceId && pathname === '/speakers') {
+ setSelectedSpeaker(null)
+ } else {
+ setSelectedSpeaker(speaker)
+ }
+ }
- if (pathname === '/speakers' && isLargeScreen) e.preventDefault()
+ setDevaBotVisible(false)
+ }
- if (isLargeScreen) {
- if (selectedSpeaker?.sourceId === speaker.sourceId && pathname === '/speakers') {
- setSelectedSpeaker(null)
- } else {
- setSelectedSpeaker(speaker)
- }
- }
+ return (
+
+
- setDevaBotVisible(false)
- }}
- >
-
-
{speaker.name}
-
- ))}
-
-
-
+
setDevaBotVisible('Recommend speakers who know about ')}>
diff --git a/devcon-app/src/components/domain/app/dc7/speakers/recommendations.tsx b/devcon-app/src/components/domain/app/dc7/speakers/recommendations.tsx
new file mode 100644
index 000000000..033fab960
--- /dev/null
+++ b/devcon-app/src/components/domain/app/dc7/speakers/recommendations.tsx
@@ -0,0 +1,183 @@
+import React, { useMemo, useState } from 'react'
+import SwipeToScroll from 'lib/components/event-schedule/swipe-to-scroll'
+import { Speaker as SpeakerType } from 'types/Speaker'
+import { Session as SessionType } from 'types/Session'
+import { Link } from 'components/common/link'
+import Image from 'next/image'
+import { useQuery } from '@tanstack/react-query'
+import { useAccountContext } from 'context/account-context'
+import { APP_CONFIG } from 'utils/config'
+import { Separator } from 'lib/components/ui/separator'
+import cn from 'classnames'
+import moment from 'moment'
+import { useDraggableLink } from 'lib/hooks/useDraggableLink'
+import css from './speakers.module.scss'
+import { FancyLoader } from 'lib/components/loader/loader'
+
+interface Props {
+ speakers: SpeakerType[]
+ selectedSpeaker?: SpeakerType | null
+ standalone?: boolean
+ onSpeakerSelect?: (e: any, speaker: SpeakerType) => void
+}
+
+export function RecommendedSpeakers({ speakers, selectedSpeaker, standalone, onSpeakerSelect }: Props) {
+ const { account } = useAccountContext()
+ const [filter, setFilter] = useState<'featured' | 'social'>('featured')
+ const draggableLink = useDraggableLink()
+
+ const featuredSpeakers = useMemo(
+ () =>
+ speakers
+ ?.filter(speaker =>
+ speaker.sessions?.some(session => session.featured && moment(session.slot_start).isAfter(moment()))
+ )
+ .sort(() => Math.random() - 0.5),
+ [speakers]
+ )
+
+ const { data: recommended, isLoading } = useQuery({
+ queryKey: ['account', 'speakers', 'recommended', account?.id],
+ queryFn: async () => {
+ if (!account?.id) {
+ console.log('Not logged in... No recommendations')
+ return []
+ }
+
+ try {
+ const response = await fetch(`${APP_CONFIG.API_BASE_URL}/account/speakers/recommended`, {
+ method: 'GET',
+ credentials: 'include',
+ })
+
+ const { data } = await response.json()
+ return data.sort(() => Math.random() - 0.5)
+ } catch (error) {
+ console.error('Error fetching recommended speakers', error)
+ return []
+ }
+ },
+ })
+
+ const speakerList = useMemo(() => {
+ if (filter === 'featured') return featuredSpeakers
+ if (filter === 'social') return recommended
+ return []
+ }, [filter, featuredSpeakers, recommended])
+
+ return (
+ <>
+
+ Speaker Highlights{' '}
+ {standalone && (
+
+
Go to Speakers
+
+ )}
+
+
+ {standalone && (
+
+
+
setFilter('featured')}
+ >
+ Featured
+
+
+
+
+ {[
+ {
+ id: 'social',
+ name: 'Onchain Social',
+ },
+ ].map(({ id, name }) => {
+ return (
+
{
+ setFilter(id.toLowerCase() as 'featured' | 'social')
+ }}
+ >
+ {name}
+
+ )
+ })}
+
+
+ )}
+
+
+
+
+ {filter === 'social' && isLoading && (
+
+
+
+ )}
+ {filter === 'social' && !isLoading && !speakerList?.length && (
+
+
+
+ Connect your wallet
+ {' '}
+ to include your onchain social graph. Your social connections are based on{' '}
+
+ Farcaster
+
+ ,{' '}
+
+ Lens
+ {' '}
+ and{' '}
+
+ Ethereum Follow Protocol
+
+ .
+
+
+ )}
+ {speakerList?.map((speaker: SpeakerType, index: number) => (
+
onSpeakerSelect?.(e, speaker)}
+ >
+
+
{speaker.name}
+
+ ))}
+
+
+
+ >
+ )
+}