diff --git a/src/components/mindset/components/LandingPage/index.tsx b/src/components/mindset/components/LandingPage/index.tsx index 6227d6146..7c33fbcbd 100644 --- a/src/components/mindset/components/LandingPage/index.tsx +++ b/src/components/mindset/components/LandingPage/index.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components' import { Flex } from '~/components/common/Flex' import { NODE_ADD_ERROR } from '~/constants' import { api } from '~/network/api' -import { getSchemaAll } from '~/network/fetchSourcesData' +import { getNodes, getSchemaAll } from '~/network/fetchSourcesData' import { useDataStore } from '~/stores/useDataStore' import { useMindsetStore } from '~/stores/useMindsetStore' import { useSchemaStore } from '~/stores/useSchemaStore' @@ -12,6 +12,7 @@ import { SubmitErrRes } from '~/types' import { colors } from '~/utils/colors' import { ChevronRight } from '../Icon/ChevronRight' import { isValidMediaUrl } from './utils' +import { VideoCard } from '../VideoCard' export type FormData = { input: string @@ -21,6 +22,26 @@ export type FormData = { latitude: string } +interface EpisodeProperties { + date: number + episode_title: string + image_url: string + media_url: string + pubkey: string + source_link: string + status: string +} + +interface Node { + node_type: string + properties?: EpisodeProperties +} + +export interface ApiResponse { + edges: never[] + nodes: Node[] +} + const handleSubmitForm = async (data: FieldValues): Promise => { const endPoint = 'add_node' @@ -44,13 +65,27 @@ export const LandingPage = () => { const [inputValue, setInputValue] = useState('') const [error, setError] = useState(false) const [requestError, setRequestError] = useState('') + const [episodes, setEpisodes] = useState([]) const { setRunningProjectId } = useDataStore((s) => s) const { setSelectedEpisodeId, setSelectedEpisodeLink } = useMindsetStore((s) => s) const { setSchemas } = useSchemaStore((s) => s) + const filterAndSortEpisodes = (data: ApiResponse): EpisodeProperties[] => + data.nodes + .filter((node) => node.node_type.toLowerCase() === 'episode' && node.properties?.date) + .map((node) => node.properties!) + .sort((a, b) => b.date - a.date) + .slice(0, 3) + useEffect(() => { const fetchSchemaData = async () => { try { + const res: ApiResponse = await getNodes() + + const topEpisodes = filterAndSortEpisodes(res) + + setEpisodes(topEpisodes) + const response = await getSchemaAll() setSchemas(response.schemas.filter((schema) => !schema.is_deleted)) @@ -69,10 +104,12 @@ export const LandingPage = () => { setError(value !== '' && !isValidMediaUrl(value)) } - const handleSubmit = async () => { - if (isValidMediaUrl(inputValue)) { + const handleSubmit = async (url?: string) => { + const source = url || inputValue + + if (isValidMediaUrl(source)) { try { - const res = await handleSubmitForm({ source: inputValue }) + const res = await handleSubmitForm({ source }) if (res.data.project_id) { setRunningProjectId(res.data.project_id) @@ -80,7 +117,7 @@ export const LandingPage = () => { if (res.data.ref_id) { setSelectedEpisodeId(res.data.ref_id) - setSelectedEpisodeLink(inputValue) + setSelectedEpisodeLink(source) } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -94,7 +131,7 @@ export const LandingPage = () => { if (res.data.ref_id) { setSelectedEpisodeId(res.data.ref_id) - setSelectedEpisodeLink(inputValue) + setSelectedEpisodeLink(source) } } else if (err instanceof Error) { errorMessage = err.message @@ -116,11 +153,22 @@ export const LandingPage = () => { placeholder="Paste podcast or video link" value={inputValue} /> - + handleSubmit() : undefined}> {requestError &&
{requestError}
} + + {episodes.map((episode) => ( + handleSubmit(episode?.source_link)} + subtitle="Subtitle for episode seed" + title={(episode?.episode_title as string) || ''} + /> + ))} + ) } @@ -154,7 +202,7 @@ const Title = styled(Flex)` const Input = styled.input<{ error?: boolean }>` width: 100%; - max-width: 450px; + max-width: 648px; padding: 12px 28px 12px 16px; border-radius: 100px; border: 1px solid ${(props) => (props.error ? 'red' : colors.DIVIDER_4)}; @@ -162,11 +210,9 @@ const Input = styled.input<{ error?: boolean }>` color: ${colors.white}; font-family: Barlow; font-size: 16px; - &::placeholder { color: ${colors.INPUT_PLACEHOLDER}; } - &:focus { outline: none; border-color: ${(props) => (props.error ? 'red' : colors.primaryBlue)}; @@ -175,7 +221,7 @@ const Input = styled.input<{ error?: boolean }>` const InputWrapper = styled.div` position: relative; - width: 450px; + width: 648px; display: flex; align-items: center; ` @@ -188,10 +234,19 @@ const IconWrapper = styled.div<{ error?: boolean }>` color: ${colors.white}; font-size: 20px; cursor: ${(props) => (props.error ? 'not-allowed' : 'pointer')}; - svg { width: 8px; height: 17px; color: ${colors.GRAY6}; } ` + +const SeedQuestionsWrapper = styled.div` + display: flex; + align-items: center; + justify-content: center; + gap: 16px; + margin-top: 20px; + max-width: 648px; + height: 237px; +` diff --git a/src/components/mindset/components/VideoCard/index.tsx b/src/components/mindset/components/VideoCard/index.tsx new file mode 100644 index 000000000..d40c096d6 --- /dev/null +++ b/src/components/mindset/components/VideoCard/index.tsx @@ -0,0 +1,93 @@ +import styled from 'styled-components' +import { Flex } from '~/components/common/Flex' +import { colors } from '~/utils/colors' + +interface VideoCardProps { + imageUrl: string + title: string + subtitle: string + onClick: () => void +} + +export const VideoCard = ({ imageUrl, title, subtitle, onClick }: VideoCardProps) => { + const truncatedTitle = title.length > 35 ? `${title.substring(0, 32)}...` : title + const truncatedSubtitle = subtitle.length > 50 ? `${subtitle.substring(0, 47)}...` : subtitle + + return ( + + + + + + {truncatedTitle} + {truncatedSubtitle} + + + ) +} + +const CardWrapper = styled(Flex)` + background: ${colors.BG1}; + width: 170px; + height: 200px; + color: ${colors.white}; + padding: 16px; + border-radius: 8px; + cursor: pointer; + display: flex; + flex-direction: column; + align-items: center; + &:hover { + background: ${colors.SEEDQUESTION_HOVER}; + } + &:active { + background: ${colors.SEEDQUESTION}; + } +` + +const ImageWrapper = styled.div` + width: 100%; + height: 140px; /* Fixed height for images */ + border-radius: 6px; + overflow: hidden; + margin-bottom: 12px; + display: flex; + justify-content: center; + align-items: center; +` + +const CardImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; +` + +const TextWrapper = styled(Flex)` + flex-direction: column; + justify-content: flex-start; + gap: 8px; +` + +const CardTitle = styled.p` + font-family: Inter; + font-size: 16px; + font-weight: 500; + line-height: 19px; + color: ${colors.white}; + margin: 0; + white-space: wrap; + overflow: hidden; + text-overflow: ellipsis; +` + +const CardSubtitle = styled.p` + font-family: Inter; + font-size: 14px; + font-weight: 400; + line-height: 17px; + color: ${colors.GRAY6}; + margin: 0; + white-space: wrap; + overflow: hidden; + text-overflow: ellipsis; +` diff --git a/src/network/fetchSourcesData/index.ts b/src/network/fetchSourcesData/index.ts index 9f083aa47..9b0b4bb94 100644 --- a/src/network/fetchSourcesData/index.ts +++ b/src/network/fetchSourcesData/index.ts @@ -10,6 +10,7 @@ import { SubmitErrRes, } from '~/types' import { api } from '../api' +import { ApiResponse } from '~/components/mindset/components/LandingPage' type TradarParams = { skip?: string @@ -179,6 +180,14 @@ export interface UpdateSchemaParams { } } +export const getNodes = async (): Promise => { + const url = `/prediction/graph/search?node_type=['Episode']&&include_properties=true&&includecontent=true` + + const response = await api.get(url) + + return response +} + export const editNodeSchemaUpdate = async (ref_id: string, data: UpdateSchemaParams) => { const response = await api.put(`/schema/${ref_id}`, JSON.stringify(data))