mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-28 10:44:23 +01:00
266 lines
13 KiB
TypeScript
266 lines
13 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { MdLocalFireDepartment } from 'react-icons/md';
|
|
import { ModalCard } from 'src/Components/Modals/ModalsContainer/ModalsContainer';
|
|
import { useAppDispatch, useAppSelector, useMediaQuery } from 'src/utils/hooks';
|
|
import { openModal, scheduleModal } from 'src/redux/features/modals.slice';
|
|
import { setProject } from 'src/redux/features/project.slice';
|
|
import Button from 'src/Components/Button/Button';
|
|
import ProjectCardSkeleton from './ProjectDetailsCard.Skeleton'
|
|
// import VoteButton from 'src/features/Projects/pages/ProjectPage/VoteButton/VoteButton';
|
|
import { NotificationsService, Wallet_Service } from 'src/services'
|
|
import { ProjectLaunchStatusEnum, ProjectPermissionEnum, useProjectDetailsQuery } from 'src/graphql';
|
|
import Lightbox from 'src/Components/Lightbox/Lightbox'
|
|
import linkifyHtml from 'linkify-html';
|
|
import ErrorMessage from 'src/Components/Errors/ErrorMessage/ErrorMessage';
|
|
import { setVoteAmount } from 'src/redux/features/vote.slice';
|
|
import { numberFormatter } from 'src/utils/helperFunctions';
|
|
import { MEDIA_QUERIES } from 'src/utils/theme';
|
|
import { FaDiscord, } from 'react-icons/fa';
|
|
import { FiEdit2, FiGithub, FiGlobe, FiTwitter } from 'react-icons/fi';
|
|
import CopyToClipboard from 'react-copy-to-clipboard';
|
|
import Badge from 'src/Components/Badge/Badge';
|
|
import Avatar from 'src/features/Profiles/Components/Avatar/Avatar';
|
|
import { Link } from 'react-router-dom';
|
|
import { createRoute } from 'src/utils/routing';
|
|
import { IoMdClose } from 'react-icons/io';
|
|
|
|
|
|
interface Props extends ModalCard {
|
|
projectId: number;
|
|
}
|
|
|
|
export default function ProjectDetailsCard({ direction, projectId, ...props }: Props) {
|
|
|
|
const dispatch = useAppDispatch();
|
|
const [screenshotsOpen, setScreenshotsOpen] = useState(-1);
|
|
|
|
|
|
const { isWalletConnected } = useAppSelector(state => ({
|
|
isWalletConnected: state.wallet.isConnected,
|
|
}));
|
|
const isMdScreen = useMediaQuery(MEDIA_QUERIES.isMedium)
|
|
|
|
const { data, loading, error } = useProjectDetailsQuery({
|
|
variables: { projectId: projectId!, projectTag: null },
|
|
onCompleted: data => {
|
|
dispatch(setProject(data.getProject))
|
|
},
|
|
onError: () => {
|
|
dispatch(setProject(null));
|
|
},
|
|
skip: !Boolean(projectId)
|
|
});
|
|
|
|
|
|
const closeModal = () => {
|
|
props.onClose?.();
|
|
}
|
|
|
|
|
|
if (error)
|
|
return <div
|
|
className={`modal-card max-w-[768px] ${props.isPageModal && !isMdScreen && 'rounded-0 w-full min-h-screen'}`}
|
|
>
|
|
<div className="p-64">
|
|
<ErrorMessage type='fetching' message='Something Wrong happened while fetching project details, please try refreshing the page' />
|
|
</div>
|
|
</div>
|
|
|
|
if (loading || !data?.getProject)
|
|
return <ProjectCardSkeleton onClose={closeModal} direction={direction} isPageModal={props.isPageModal} />;
|
|
|
|
|
|
const project = data.getProject;
|
|
|
|
const links = [
|
|
{
|
|
value: project.discord,
|
|
text: project.discord,
|
|
icon: FaDiscord,
|
|
colors: "bg-violet-100 text-violet-900",
|
|
},
|
|
{
|
|
value: project.website,
|
|
text: project.website?.replace(/(^\w+:|^)\/\//, '').replace(/\/$/, ""),
|
|
icon: FiGlobe,
|
|
colors: "bg-gray-100 text-gray-900",
|
|
url: project.website
|
|
},
|
|
{
|
|
value: project.twitter,
|
|
text: project.twitter,
|
|
icon: FiTwitter,
|
|
colors: "bg-blue-100 text-blue-500",
|
|
url: project.twitter
|
|
},
|
|
{
|
|
value: project.github,
|
|
text: project.github,
|
|
icon: FiGithub,
|
|
colors: "bg-pink-100 text-pink-600",
|
|
url: project.github
|
|
},
|
|
];
|
|
|
|
const onVote = (votes?: number) => {
|
|
dispatch(setVoteAmount(votes ?? 10));
|
|
dispatch(openModal({
|
|
Modal: 'VoteCard', props: {
|
|
projectId: project.id,
|
|
title: project.title,
|
|
initVotes: votes
|
|
}
|
|
}))
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={`modal-card max-w-[676px] ${(props.isPageModal && !isMdScreen) && '!rounded-0 w-full min-h-screen'}`}
|
|
>
|
|
{/* Cover Image */}
|
|
<div className="relative h-[120px] lg:h-[80px]">
|
|
<img className="w-full h-full object-cover" src={project.cover_image} alt="" />
|
|
<div className="absolute top-16 md:top-24 left-24 flex gap-8 bg-gray-800 bg-opacity-60 text-white rounded-48 py-4 px-12 text-body6 font-medium">
|
|
{project.launch_status === ProjectLaunchStatusEnum.Launched && `🚀 Launched`}
|
|
{project.launch_status === ProjectLaunchStatusEnum.Wip && `🔧 WIP`}
|
|
</div>
|
|
<div className="absolute top-16 md:top-24 right-24 flex gap-8">
|
|
{project.permissions.includes(ProjectPermissionEnum.UpdateInfo) &&
|
|
<Link className="w-32 h-32 bg-gray-800 bg-opacity-60 text-white rounded-full hover:bg-opacity-40 text-center flex flex-col justify-center items-center" onClick={() => props.onClose?.()} to={createRoute({ type: "edit-project", id: project.id })}><FiEdit2 /></Link>}
|
|
<button className="w-32 h-32 bg-gray-800 bg-opacity-60 text-white rounded-full hover:bg-opacity-40 text-center flex flex-col justify-center items-center" onClick={closeModal}><IoMdClose className=' inline-block' /></button>
|
|
</div>
|
|
</div>
|
|
<div className="p-24 flex flex-col gap-24">
|
|
|
|
{/* Title & Basic Info */}
|
|
<div className="flex flex-col mt-[-80px] md:flex-row md:mt-0 gap-24 md:items-center relative">
|
|
<div className="flex-shrink-0 w-[108px] h-[108px]">
|
|
<img className="w-full h-full border-2 border-white rounded-24" src={project.thumbnail_image} alt="" />
|
|
</div>
|
|
<div className='flex flex-col gap-8 items-start justify-between'>
|
|
<a href={project.website} target='_blank' rel="noreferrer"><h3 className="text-body1 font-bold">{project.title}</h3></a>
|
|
<p className="text-body4 text-gray-600">{project.tagline}</p>
|
|
<div>
|
|
<span className="font-medium text-body4 text-gray-600">{project.category.icon} {project.category.title}</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex-shrink-0 w-full md:w-auto md:flex ml-auto gap-16 self-stretch">
|
|
{/* <Button color='primary' size='md' className=" my-16" href={project.website} newTab >Visit <BsJoystick /></Button> */}
|
|
{/* <VoteButton onVote={onVote} /> */}
|
|
{/* <VoteButton fullWidth votes={project.votes_count} direction='vertical' onVote={onVote} /> */}
|
|
{/* {isWalletConnected ?
|
|
:
|
|
<Button onClick={onConnectWallet} size='md' className="border border-gray-200 bg-gray-100 hover:bg-gray-50 active:bg-gray-100 my-16"><AiFillThunderbolt className='inline-block text-thunder transform scale-125' /> Connect Wallet to Vote</Button>
|
|
} */}
|
|
<Button fullWidth variant='outline' color='gray' className='!px-8' onClick={() => onVote()}>
|
|
<div className="flex justify-center items-center gap-8 md:flex-col ">
|
|
<MdLocalFireDepartment />{<span className="align-middle w-[4ch]"> {numberFormatter(project.votes_count)}</span>}
|
|
</div>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{/* About */}
|
|
<div>
|
|
<p className="text-body6 uppercase font-medium text-gray-400 mb-8">About</p>
|
|
<div className=" text-body4 text-gray-600 leading-normal whitespace-pre-line" dangerouslySetInnerHTML={{
|
|
__html: linkifyHtml(project.description, {
|
|
className: ' text-blue-500 underline',
|
|
defaultProtocol: 'https',
|
|
target: "_blank",
|
|
rel: 'noreferrer'
|
|
})
|
|
}}></div>
|
|
|
|
{/* Links */}
|
|
<div className="mt-16 flex flex-wrap gap-16">
|
|
{links.filter(link => !!link.value).map((link, idx) =>
|
|
(link.url ? <a
|
|
key={idx}
|
|
href={link.url!}
|
|
className={`w-40 aspect-square rounded-full flex justify-center items-center ${link.colors}`}
|
|
target='_blank'
|
|
rel="noreferrer">
|
|
<link.icon className="scale-125" />
|
|
</a>
|
|
:
|
|
<CopyToClipboard
|
|
text={link.value!}
|
|
onCopy={() => NotificationsService.info(" Copied to clipboard", { icon: "📋" })}
|
|
>
|
|
<button
|
|
key={idx}
|
|
onClick={() => { }}
|
|
className={`w-40 aspect-square rounded-full flex justify-center items-center ${link.colors}`}
|
|
>
|
|
<link.icon className="scale-125" />
|
|
</button>
|
|
</CopyToClipboard>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{project.screenshots.length > 0 && <>
|
|
<div className="">
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 justify-items-center">
|
|
{project.screenshots.slice(0, 4).map((screenshot, idx) => <div
|
|
key={idx}
|
|
className="w-full relative pt-[56%] cursor-pointer bg-gray-100 border rounded-10 overflow-hidden"
|
|
onClick={() => setScreenshotsOpen(idx)}
|
|
>
|
|
<img src={screenshot} className="absolute top-0 left-0 w-full h-full object-cover" alt='' />
|
|
</div>)}
|
|
</div>
|
|
</div>
|
|
<Lightbox
|
|
images={project.screenshots}
|
|
isOpen={screenshotsOpen !== -1}
|
|
initOpenIndex={screenshotsOpen}
|
|
onClose={() => setScreenshotsOpen(-1)}
|
|
/>
|
|
</>}
|
|
|
|
{/* {project.capabilities.length > 0 &&
|
|
<div>
|
|
<p className="text-body6 uppercase font-medium text-gray-400 mb-8">CAPABILITIES</p>
|
|
<div className="flex flex-wrap gap-8">
|
|
{project.capabilities.map(cap => <Badge key={cap.id} size='sm'>{cap.icon} {cap.title}</Badge>)}
|
|
</div>
|
|
</div>}
|
|
*/}
|
|
<Button color='gray' fullWidth href={createRoute({ type: "project", tag: project.hashtag })} onClick={props.onClose}>Show More</Button>
|
|
{/* {project.members.length > 0 &&
|
|
<div>
|
|
<p className="text-body6 uppercase font-medium text-gray-400 mb-8">MAKERS</p>
|
|
<div className="flex flex-wrap gap-8">
|
|
{project.members.map(m => <Link key={m.user.id} to={createRoute({ type: "profile", id: m.user.id, username: m.user.name })}>
|
|
<Avatar
|
|
width={40}
|
|
src={m.user.avatar}
|
|
renderTooltip={() => <div className='bg-white px-12 py-8 border border-gray-200 rounded-12 flex flex-wrap gap-12 shadow-lg'>
|
|
<Avatar width={48} src={m.user.avatar} />
|
|
<div className='overflow-hidden'>
|
|
<p className={`text-black font-medium overflow-hidden text-ellipsis`}>{m.user.name}</p>
|
|
<p className={`text-body6 text-gray-600`}>{m.user.jobTitle}</p>
|
|
</div>
|
|
</div>}
|
|
/>
|
|
</Link>)}
|
|
</div>
|
|
</div>} */}
|
|
{/* <div className="text-center">
|
|
<h3 className="text-body4 font-regular">Are you the creator of this project</h3>
|
|
<Button
|
|
color='gray'
|
|
size='md'
|
|
className="my-16"
|
|
href={`https://airtable.com/shr67F20KG9Gdok6d?prefill_app_name=${project.title}&prefill_app_link=${project.website}`}
|
|
newTab
|
|
// onClick={onClaim}
|
|
>Claim 🖐</Button>
|
|
</div> */}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|