feat: refactor project structure

Refactored the project structure so that each page has its own tree of components and a global "Components" folder for the components that is used by more than one page.
- Added an "assets" directory that exports all static images/icons/fonts/...etc
This commit is contained in:
MTG2000
2021-12-30 15:12:40 +02:00
parent 5ae1da6369
commit 43bfab177e
51 changed files with 71 additions and 60 deletions

View File

@@ -1,17 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Claim_CopySignatureCard from './Claim_CopySignatureCard';
import { ModalsDecorator } from '.storybook/helpers'
export default {
title: 'Claim/Copy Signature Card',
component: Claim_CopySignatureCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof Claim_CopySignatureCard>;
const Template: ComponentStory<typeof Claim_CopySignatureCard> = (args) => <Claim_CopySignatureCard {...args} />;
export const Default = Template.bind({});

View File

@@ -1,60 +0,0 @@
import { motion } from 'framer-motion'
import { Direction, ModalId, replaceModal } from '../../redux/features/modals.slice';
import { useAppDispatch, useAppSelector } from '../../utils/hooks';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
import CopyToClipboard from 'src/Components/Shared/CopyToClipboard/CopyToClipboard'
import { useCallback } from 'react';
import { IoClose } from 'react-icons/io5';
export default function Claim_CopySignatureCard({ onClose, direction, ...props }: ModalCard) {
const dispatch = useAppDispatch();
const { projectName, image } = useAppSelector(state => ({ projectName: state.project.project?.title, image: state.project.project?.thumbnail_image, }))
const generatedHash = '0x000330RaaSt302440zxc327jjiaswf19987183345pRReuBNksbaaueee'
const handleNext = useCallback(() => {
dispatch(replaceModal({
modalId: ModalId.Claim_Submitted,
direction: Direction.NEXT
}))
}, [dispatch])
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[343px] p-24 rounded-xl relative"
>
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold'>Claim this project</h2>
<div className="flex justify-center my-32">
<img
src={image}
className='w-80 h-80 object-cover rounded-2xl'
alt="" />
</div>
<p className="text-body4 text-center px-16">
Good job! Now paste this on the webpage
<a className="font-bold" href="www.projectname.com/"
target='_blank' rel='noreferrer'
> www.projectname.com/</a>
</p>
<div className="input-wrapper mt-32">
<input
className="input-field overflow-ellipsis"
value={generatedHash}
/>
<CopyToClipboard text={generatedHash} />
</div>
<div className="mt-32">
<button className='btn btn-primary w-full' onClick={handleNext}>Submit for review</button>
</div>
</motion.div>
)
}

View File

@@ -1,17 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Claim_FundWithdrawCard from './Claim_FundWithdrawCard';
import { ModalsDecorator } from '.storybook/helpers'
export default {
title: 'Claim/Fund Withdraw Card',
component: Claim_FundWithdrawCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof Claim_FundWithdrawCard>;
const Template: ComponentStory<typeof Claim_FundWithdrawCard> = (args) => <Claim_FundWithdrawCard {...args} />;
export const Default = Template.bind({});

View File

@@ -1,38 +0,0 @@
import { motion } from 'framer-motion'
// import { useAppDispatch } from '../../utils/hooks';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
export default function Claim_FundWithdrawCard({ onClose, direction, ...props }: ModalCard) {
//const dispatch = useAppDispatch();
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[260px] py-16 px-24 rounded-xl relative"
>
<div className="flex justify-center my-16">
<img
src={'assets/icons/lightning-small.svg'}
className='w-48 h-48 object-cover rounded-full'
alt="" />
</div>
<p className="text-h4 text-center font-bold">
2,220 sats
</p>
<p className="text-body4 text-center text-gray-400">
2.78$
</p>
<div className="mt-16 flex flex-col gap-8">
<button className='btn btn-primary w-full shadow-xs' >Fund</button>
<button className='btn border w-full shadow-xs' >Withdraw</button>
</div>
</motion.div>
)
}

View File

@@ -1,17 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Claim_GenerateSignatureCard from './Claim_GenerateSignatureCard';
import { ModalsDecorator } from '.storybook/helpers'
export default {
title: 'Claim/Generate Signature Card',
component: Claim_GenerateSignatureCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof Claim_GenerateSignatureCard>;
const Template: ComponentStory<typeof Claim_GenerateSignatureCard> = (args) => <Claim_GenerateSignatureCard {...args} />;
export const Default = Template.bind({});

View File

@@ -1,59 +0,0 @@
import { motion } from 'framer-motion'
import { Direction, ModalId, replaceModal } from '../../redux/features/modals.slice';
import { useAppDispatch, useAppSelector } from '../../utils/hooks';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
import { useCallback, useEffect } from 'react';
import { IoClose } from 'react-icons/io5';
export default function Claim_GenerateSignatureCard({ onClose, direction, ...props }: ModalCard) {
const dispatch = useAppDispatch();
const { projectName, image } = useAppSelector(state => ({ projectName: state.project.project?.title, image: state.project.project?.thumbnail_image, }))
const handleNext = useCallback(() => {
dispatch(replaceModal({
modalId: ModalId.Claim_CopySignature,
direction: Direction.NEXT
}))
}, [dispatch])
useEffect(() => {
// const timeout = setTimeout(handleNext, 3000)
// return () => clearTimeout(timeout)
}, [handleNext])
//const onCopy = () => {
// // Copy to Clipboard
// setTimeout(handleNext, 2000)
//}
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[343px] p-24 rounded-xl relative"
>
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold'>Claim this project</h2>
<div className="flex justify-center my-32">
<img
src={image}
className='w-80 h-80 object-cover rounded-2xl'
alt="" />
</div>
<p className="text-body4 text-center px-16">
To claim ownership of <span className="font-bold">{projectName}</span> and its tips, you need to sign a message and paste this on the project website so we can verify you are the real ownership
</p>
<div className="mt-32">
<button className='btn btn-primary w-full' onClick={handleNext}>Generate Signature</button>
</div>
</motion.div>
)
}

View File

@@ -1,17 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Claim_SubmittedCard from './Claim_SubmittedCard';
import { ModalsDecorator } from '.storybook/helpers'
export default {
title: 'Claim/Submitted Card',
component: Claim_SubmittedCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof Claim_SubmittedCard>;
const Template: ComponentStory<typeof Claim_SubmittedCard> = (args) => <Claim_SubmittedCard {...args} />;
export const Default = Template.bind({});

View File

@@ -1,39 +0,0 @@
import { motion } from 'framer-motion'
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
import { IoClose } from 'react-icons/io5';
export default function Claim_SubmittedCard({ onClose, direction, ...props }: ModalCard) {
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[343px] p-24 rounded-xl relative"
>
<IoClose
className='absolute text-body2 top-24 right-24 hover:cursor-pointer'
onClick={onClose} />
<h2 className='text-h5 font-bold'>Submitted For Review</h2>
<div className="flex justify-center my-32">
<img
src="assets/icons/flag-icon.svg"
className='w-80 h-80'
alt="success" />
</div>
<p className="text-body4 text-center">
Great work! your claim to <span className="font-bold">Application Name</span> has been submitted for review.
<br />
Check back soon to see if it was successful.
</p>
</motion.div>
)
}

View File

@@ -1,15 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Categories from './Categories';
export default {
title: 'Explore Page/Categories',
component: Categories,
} as ComponentMeta<typeof Categories>;
const Template: ComponentStory<typeof Categories> = (args) => <Categories />;
export const Default = Template.bind({});

View File

@@ -1,20 +0,0 @@
import { useQuery } from '@apollo/client';
import { ALL_CATEGORIES_QUERY, ALL_CATEGORIES_QUERY_RES } from './query';
export default function Categories() {
const { data, loading } = useQuery<ALL_CATEGORIES_QUERY_RES>(ALL_CATEGORIES_QUERY);
const handleClick = (categoryId: string) => {
}
if (loading)
return null;
return (
<div className="flex gap-12 flex-wrap">
{data?.allCategories.map(category => <span key={category.id} className="chip hover:cursor-pointer hover:bg-gray-200" onClick={() => handleClick(category.id)}>{category.title}</span>)}
</div>
)
}

View File

@@ -1,15 +0,0 @@
import { gql } from "@apollo/client";
import { ProjectCategory } from "src/utils/interfaces";
export const ALL_CATEGORIES_QUERY = gql`
query AllCategories {
allCategories {
id
title
}
}
`;
export type ALL_CATEGORIES_QUERY_RES = {
allCategories: ProjectCategory[];
};

View File

@@ -1,18 +0,0 @@
import Categories from "./Categories/Categories";
import Header from "./Header/Header";
import ProjectsSection from "./ProjectsSection/ProjectsSection";
export default function ExplorePage() {
return (
<>
<div className="px-32">
<Header />
</div>
<div className="my-40 px-32">
<Categories />
</div>
<ProjectsSection />
</>
)
}

View File

@@ -1,15 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Header from './Header';
export default {
title: 'Explore Page/Header',
component: Header,
} as ComponentMeta<typeof Header>;
const Template: ComponentStory<typeof Header> = (args) => <Header />;
export const Default = Template.bind({});

View File

@@ -1,59 +0,0 @@
import Button from 'src/Components/Shared/Button/Button'
const headerLinks = [
{
title: "A fun directory of Lightning Enabled Applications on the Open Web",
img:
"https://s3-alpha-sig.figma.com/img/07b8/5d84/145942255afd215b3da26dbbf1dd03bd?Expires=1638144000&Signature=Cl1DUQJIUsrrFi48M~qU1r3Z0agGdy-uiNUao5g8-nu34QtoyWTFPXvaH3naSZBYqcPyKFq1jaXF6Mw1uj1hdWwGhXhMPLnKNJFFrGViVXhXu-3YeCPY9p4-IcieFJBZPVA~zDY8zxY5b06loWsINAVx4eMHRAhSWl~~Mca5PjlSXloiYrT00W-6c9m8gevfMMX~PsHQedzwYzg0j2DlnhPX8LbRkli1G2OxtCaFwo3~HGHXIlFGuGU1uXRvi1qBWrdjdsuWgIly1ekcFfJWAKmwYXk06EtCmfWRgGYbD7cBK~lwOkFofbf1LW0yqLv0hr4svwToH~3FiHenrCF-1g__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA",
link: {
content: "Submit App",
url: "#",
},
},
{
title:
"Join the next wave of the Lightning Network in Novembers Shock the Web hackathon",
img: 'https://s3-alpha-sig.figma.com/img/be1b/cd75/1baa911b3875134c0889d6755c4ba2cb?Expires=1638748800&Signature=DOiLciAA95w8gOvAowjiiR-ZPbmV1oGSRRK8YpE4ALMoe47pL7DifQxZvL1LQn~NRa0aLMoMk61521fbbGJeDAwk~j6fIm~iZAlMzQn7DdWy0wFR0uLQagOgpIiIXO-w8CeC14VoW-hrjIX5mDmOonJzkfoftGqIF1WCOmP2EuswyJpIngFdLb15gCex4Necs3vH2cuD9iSgWG2za97KfdXZP79ROyk2EN9Q3~a7RT4FTBBIlgKDLuFGSVRxReXVNajn~XSxBJh2de9dFVa3tOXkwJXu3jb0G4x-wRCaG-KmBhUOemuGtu5Fumh6goktGh~bIDwoHeUBVKFHAzaYgw__&Key-Pair-Id=APKAINTVSUGEWH5XD5UA', link: {
content: "Register Now",
url: "#",
},
},
];
export default function Header() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-40 justify-center md:justify-between">
<div className="rounded-20 h-[280px] relative overflow-hidden p-24 flex flex-col items-start justify-end">
<img
className="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src={headerLinks[0].img}
alt=""
/>
<div className="w-full h-full object-cover bg-gradient-to-t from-gray-900 absolute top-0 left-0 z-[-1]"></div>
<h3 className="text-white text-h3 max-w-[80%]">{headerLinks[0].title}</h3>
<Button
color='primary'
href={headerLinks[0].link.url}
className="font-regular mt-36"
>
{headerLinks[0].link.content}
</Button>
</div>
<div className="hidden md:flex flex-col rounded-20 h-[280px] relative overflow-hidden p-24 items-start justify-end">
<img
className="w-full h-full object-cover absolute top-0 left-0 z-[-2]"
src={headerLinks[1].img}
alt=""
/>
<div className="w-full h-full object-cover bg-gradient-to-t from-gray-900 absolute top-0 left-0 z-[-1]"></div>
<h3 className="text-white text-h3 max-w-[80%]">
{headerLinks[1].title}
</h3>
<Button href={headerLinks[1].link.url} className="font-regular mt-36">
{headerLinks[1].link.content}
</Button>
</div>
</div>
);
}

View File

@@ -1,21 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import mockData from 'src/api/mockData.json'
import ProjectCardMini from './ProjectCardMini';
export default {
title: 'Explore Page/Project Card Mini',
component: ProjectCardMini,
} as ComponentMeta<typeof ProjectCardMini>;
const Template: ComponentStory<typeof ProjectCardMini> = (args) => <ProjectCardMini {...args} />;
export const Default = Template.bind({});
Default.args = {
project: mockData.projectsCards[0]
}

View File

@@ -1,21 +0,0 @@
import { MdLocalFireDepartment } from "react-icons/md";
import { ProjectCard } from "../../../utils/interfaces";
interface Props {
project: ProjectCard
onClick: (projectId: string) => void
}
export default function ProjectCardMini({ project, onClick }: Props) {
return (
<div className="bg-gray-25 select-none px-16 py-16 flex w-[296px] gap-16 border border-gray-200 rounded-10 hover:cursor-pointer hover:bg-gray-100" onClick={() => onClick(project.id)}>
<img src={project.thumbnail_image} alt={project.title} draggable="false" className="flex-shrink-0 w-80 h-80 bg-gray-200 border-0 rounded-8"></img>
<div className="justify-around items-start min-w-0">
<p className="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap">{project.title}</p>
<p className="text-body5 text-gray-600 font-light my-[5px]">{project.category.title}</p>
<span className="chip-small bg-warning-50 text-yellow-700 font-light text-body5 py-[3px] px-10"> <MdLocalFireDepartment className='inline-block text-fire transform text-body4 align-middle' /> {project.votes_count} </span>
</div>
</div>
);
}

View File

@@ -1,33 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import mockData from 'src/api/mockData.json'
import ProjectsRow from './ProjectsRow';
import { MdLocalFireDepartment } from 'react-icons/md';
export default {
title: 'Explore Page/ProjectsRow',
component: ProjectsRow,
} as ComponentMeta<typeof ProjectsRow>;
const Template: ComponentStory<typeof ProjectsRow> = (args) => <ProjectsRow {...args} />;
export const Hottest = Template.bind({});
Hottest.args = {
title: <>
Hottest
<MdLocalFireDepartment
className='inline-block text-fire align-bottom scale-125 ml-4 origin-bottom'
/></>,
categoryId: '2',
projects: mockData.projectsCards
}
export const Defi = Template.bind({});
Defi.args = {
title: 'DeFi',
categoryId: '33',
projects: mockData.projectsCards
}

View File

@@ -1,70 +0,0 @@
import { ReactElement, useCallback, useRef, useState } from "react";
import { ProjectCard } from "../../../utils/interfaces";
import Carousel from 'react-multi-carousel';
import { MdDoubleArrow, } from 'react-icons/md';
import { useAppDispatch } from "../../../utils/hooks";
import { ModalId, openModal } from "../../../redux/features/modals.slice";
import ProjectCardMini from "../ProjectCardMini/ProjectCardMini";
import { useResizeListener } from 'src/utils/hooks'
const responsive = {
all: {
breakpoint: { max: 5000, min: 0 },
items: (((window.innerWidth - 64) / (296 + 48))),
}
}
const calcNumItems = () => {
const items = (((window.innerWidth - 32 - 296) / (296 + 20)));
return items;
}
interface Props { title: string | ReactElement, categoryId: string, projects: ProjectCard[] }
export default function ProjectsRow({ title, categoryId, projects }: Props) {
const [carouselItmsCnt, setCarouselItmsCnt] = useState(calcNumItems);
const dispatch = useAppDispatch()
responsive.all.items = carouselItmsCnt
let drag = useRef(false);
document.addEventListener('mousedown', () => drag.current = false);
document.addEventListener('mousemove', () => drag.current = true);
const handleClick = (projectId: string) => {
if (!drag.current)
dispatch(openModal({ modalId: ModalId.Project, propsToPass: { projectId } }))
}
useResizeListener(() => {
setCarouselItmsCnt(calcNumItems());
}, [setCarouselItmsCnt])
return (
<div className='mb-48'>
<h3 className="font-bolder text-body3 mb-24 px-32">{title}
<span>
<MdDoubleArrow className='text-gray-200 ml-8 hover:cursor-pointer align-bottom transform scale-y-110 scale-x-125 origin-left' onClick={() => {
console.log(categoryId);
}} />
</span>
</h3>
<Carousel
containerClass='pl-32 pr-[-32px]'
showDots={false}
arrows={false}
responsive={responsive}
centerMode
itemClass='pb-[1px]'
>
{projects.map((project, idx) =>
<ProjectCardMini key={idx} project={project} onClick={handleClick} />
)}
</Carousel>
</div>
)
}

View File

@@ -1,14 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import ProjectsSection from './ProjectsSection';
export default {
title: 'Explore Page/Projects Section',
component: ProjectsSection,
} as ComponentMeta<typeof ProjectsSection>;
const Template: ComponentStory<typeof ProjectsSection> = (args) => <ProjectsSection />;
export const Default = Template.bind({});

View File

@@ -1,30 +0,0 @@
import ProjectsRow from "../ProjectsRow/ProjectsRow";
import { MdLocalFireDepartment } from "react-icons/md";
import { useQuery } from "@apollo/client";
import { ALL_CATEGORIES_PROJECTS_QUERY, ALL_CATEGORIES_PROJECTS_RES } from "./query";
export default function ProjectsSection() {
const { data, loading } = useQuery<ALL_CATEGORIES_PROJECTS_RES>(ALL_CATEGORIES_PROJECTS_QUERY);
if (loading || !data) return null;
return (
<div className='mt-32 lg:mt-48'>
<ProjectsRow title={<>Hottest <MdLocalFireDepartment className='inline-block text-fire align-bottom scale-125 origin-bottom' /></>}
categoryId="133123"
projects={data.newProjects} />
{data.allCategories.map(({ id, title, project, }) => {
if (project)
return <ProjectsRow
key={id}
categoryId={id}
title={title}
projects={project.map(p => ({ ...p, category: { id, title } }))} />
else return null
})}
</div>
)
}

View File

@@ -1,36 +0,0 @@
import { gql } from "@apollo/client";
import { ProjectCard } from "src/utils/interfaces";
export const ALL_CATEGORIES_PROJECTS_QUERY = gql`
query AllCategoriesProjects {
allCategories {
id
title
project {
id
thumbnail_image
title
votes_count
}
}
newProjects {
id
title
thumbnail_image
votes_count
category {
title
id
}
}
}
`;
export type ALL_CATEGORIES_PROJECTS_RES = {
newProjects: ProjectCard[];
allCategories: {
id: string;
title: string;
project: ProjectCard[];
}[];
};

View File

@@ -1,9 +1,9 @@
import { motion } from 'framer-motion'
import { Direction, ModalId, replaceModal } from '../../redux/features/modals.slice';
import { useAppDispatch } from '../../utils/hooks';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
import { Direction, ModalId, replaceModal } from 'src/redux/features/modals.slice';
import { useAppDispatch } from 'src/utils/hooks';
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { AiFillThunderbolt } from 'react-icons/ai';
import CopyToClipboard from 'src/Components/Shared/CopyToClipboard/CopyToClipboard';
import CopyToClipboard from 'src/Components/CopyToClipboard/CopyToClipboard';
import { useCallback, useEffect } from 'react';
import { IoClose } from 'react-icons/io5';

View File

@@ -1,9 +1,9 @@
import { motion } from 'framer-motion'
import { Direction, ModalId, replaceModal } from '../../redux/features/modals.slice';
import { useAppDispatch } from '../../utils/hooks';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
import { Direction, ModalId, replaceModal } from 'src/redux/features/modals.slice';
import { useAppDispatch } from 'src/utils/hooks';
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { IoLockClosed, } from 'react-icons/io5'
import Button from 'src/Components/Shared/Button/Button';
import Button from 'src/Components/Button/Button';
export default function Login_NativeWalletCard({ onClose, direction, ...props }: ModalCard) {

View File

@@ -1,7 +1,7 @@
import { motion } from 'framer-motion'
import { Direction, ModalId, replaceModal } from '../../redux/features/modals.slice';
import { useAppDispatch } from '../../utils/hooks';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
import { Direction, ModalId, replaceModal } from 'src/redux/features/modals.slice';
import { useAppDispatch } from 'src/utils/hooks';
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { AiFillThunderbolt } from 'react-icons/ai';
import Loader from 'react-loader-spinner';
import { useCallback, useEffect } from 'react';

View File

@@ -1,9 +1,9 @@
import { motion } from 'framer-motion'
import { useAppDispatch } from '../../utils/hooks';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
import { useAppDispatch } from 'src/utils/hooks';
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'
import { useCallback, useEffect } from 'react';
import { closeModal, openSceduledModal } from '../../redux/features/modals.slice';
import { connectWallet } from '../../redux/features/wallet.slice';
import { closeModal, openSceduledModal } from 'src/redux/features/modals.slice';
import { connectWallet } from 'src/redux/features/wallet.slice';
export default function Login_SuccessCard({ onClose, direction, ...props }: ModalCard) {

View File

@@ -1,19 +1,19 @@
import { AnimatePresence, motion } from "framer-motion";
import { useEffect } from "react";
import { closeModal, Direction, ModalId, removeScheduledModal } from "../../../redux/features/modals.slice";
import { useAppDispatch, useAppSelector } from "../../../utils/hooks";
import Claim_CopySignatureCard from "../../ClaimProject/Claim_CopySignatureCard";
import Claim_GenerateSignatureCard from "../../ClaimProject/Claim_GenerateSignatureCard";
import Login_ExternalWalletCard from "../../Login/Login_ExternalWalletCard";
import Login_NativeWalletCard from "../../Login/Login_NativeWalletCard";
import Login_SuccessCard from "../../Login/Login_SuccessCard";
import Login_ScanningWalletCard from "../../Login/Login_ScanningWalletCard";
import ProjectCard from "../../Project/ProjectCard";
import TipCard from "../../Tip/TipCard";
import { closeModal, Direction, ModalId, removeScheduledModal } from "src/redux/features/modals.slice";
import { useAppDispatch, useAppSelector } from "src/utils/hooks";
import Claim_CopySignatureCard from "src/pages/ProjectPage/ClaimProject/Claim_CopySignatureCard";
import Claim_GenerateSignatureCard from "src/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard";
import Login_ExternalWalletCard from "src/Components/Modals/Login/Login_ExternalWalletCard";
import Login_NativeWalletCard from "src/Components/Modals/Login/Login_NativeWalletCard";
import Login_SuccessCard from "src/Components/Modals/Login/Login_SuccessCard";
import Login_ScanningWalletCard from "src/Components/Modals/Login/Login_ScanningWalletCard";
import ProjectCard from "src/pages/ProjectPage/ProjectCard/ProjectCard";
import TipCard from "src/pages/ProjectPage/Tip/TipCard";
import Modal from "../Modal/Modal";
import { Portal } from "../Portal/Portal";
import Claim_SubmittedCard from "../../ClaimProject/Claim_SubmittedCard";
import Claim_FundWithdrawCard from "../../ClaimProject/Claim_FundWithdrawCard";
import { Portal } from "../../Portal/Portal";
import Claim_SubmittedCard from "src/pages/ProjectPage/ClaimProject/Claim_SubmittedCard";
import Claim_FundWithdrawCard from "src/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard";
export interface ModalCard {
onClose?: () => void;

View File

@@ -4,16 +4,15 @@ import { MdLocalFireDepartment } from 'react-icons/md';
import { IoExtensionPuzzle } from 'react-icons/io5';
import { AiFillThunderbolt } from 'react-icons/ai';
import { BsSearch } from "react-icons/bs";
import { FormEvent, useCallback, useEffect, useRef, useState } from "react";
import { FormEvent, useRef, useState } from "react";
import { motion } from "framer-motion";
import { GrClose } from 'react-icons/gr';
import { useAppDispatch, useAppSelector } from "../../../utils/hooks";
import { ModalId, openModal } from "../../../redux/features/modals.slice";
import { useAppDispatch, useAppSelector } from "src/utils/hooks";
import { ModalId, openModal } from "src/redux/features/modals.slice";
import { Link } from "react-router-dom";
import Button from "../Button/Button";
import { setNavHeight } from "src/redux/features/theme.slice";
import _throttle from 'lodash.throttle'
import { useResizeListener } from 'src/utils/hooks'
import { useResizeListener } from 'src/utils/hooks'
export const navLinks = [
{ text: "Explore", url: "/", icon: FaHome, color: 'text-primary-600' },

View File

@@ -1,17 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import ProjectCard from './ProjectCard';
import { ModalsDecorator } from '.storybook/helpers'
export default {
title: 'Project/Project Card',
component: ProjectCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof ProjectCard>;
const Template: ComponentStory<typeof ProjectCard> = (args) => <ProjectCard {...args} />;
export const Default = Template.bind({});

View File

@@ -1,156 +0,0 @@
import { motion } from 'framer-motion'
import { BsJoystick } from 'react-icons/bs'
import { MdClose, MdLocalFireDepartment } from 'react-icons/md';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer';
import { useQuery } from "@apollo/client";
import { useAppDispatch, useAppSelector } from '../../utils/hooks';
import { ModalId, openModal, scheduleModal } from '../../redux/features/modals.slice';
import { setProject } from '../../redux/features/project.slice';
import { connectWallet } from '../../redux/features/wallet.slice';
import Button from 'src/Components/Shared/Button/Button';
import { requestProvider } from 'webln';
import { PROJECT_BY_ID_QUERY, PROJECT_BY_ID_RES, PROJECT_BY_ID_VARS } from './query'
import { AiFillThunderbolt } from 'react-icons/ai';
export default function ProjectCard({ onClose, direction, ...props }: ModalCard) {
const dispatch = useAppDispatch();
const { loading } = useQuery<PROJECT_BY_ID_RES, PROJECT_BY_ID_VARS>(
PROJECT_BY_ID_QUERY,
{
variables: { projectId: parseInt(props.projectId) },
onCompleted: data => {
dispatch(setProject(data.getProject))
},
}
);
const { isWalletConnected, webln, project, isMobileScreen } = useAppSelector(state => ({
isWalletConnected: state.wallet.isConnected,
webln: state.wallet.provider,
project: state.project.project,
isMobileScreen: state.theme.isMobileScreen
}));
if (loading || !project) return <></>;
const onConnectWallet = async () => {
try {
const webln = await requestProvider();
if (webln) {
dispatch(connectWallet(webln));
alert("wallet connected!");
}
// Now you can call all of the webln.* methods
}
catch (err: any) {
// Tell the user what went wrong
alert(err.message);
}
}
const onTip = () => {
if (!isWalletConnected) {
dispatch(scheduleModal({ modalId: ModalId.Tip, propsToPass: { projectId: props.projectId } }))
dispatch(openModal({
modalId: ModalId.Login_ScanWallet
}))
} else
dispatch(openModal({ modalId: ModalId.Tip, propsToPass: { projectId: props.projectId } }))
}
const onClaim = () => {
if (!isWalletConnected) {
dispatch(scheduleModal({
modalId: ModalId.Claim_GenerateSignature,
propsToPass: { projectId: props.projectId },
}))
dispatch(openModal({
modalId: ModalId.Login_ScanWallet
}))
} else
dispatch(openModal({
modalId: ModalId.Claim_GenerateSignature,
propsToPass: { projectId: props.projectId },
}))
}
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className={`modal-card max-w-[768px] ${props.isPageModal && isMobileScreen && 'rounded-0 w-full min-h-screen'}`}
>
<div className="relative h-[80px] lg:h-[152px]">
<img className="w-full h-full object-cover" src={project.cover_image} alt="" />
<button className="w-[48px] h-[48px] bg-white absolute top-1/2 left-32 -translate-y-1/2 rounded-full hover:bg-gray-200 text-center" onClick={onClose}><MdClose className=' inline-block text-body2 lg:text-body1' /></button>
</div>
<div className="p-24">
<div className="flex gap-24 items-center h-[93px]">
<div className="flex-shrink-0 w-[93px] h-[93px] rounded-md overflow-hidden">
<img className="w-full h-full object-cover" src={project?.thumbnail_image} alt="" />
</div>
<div className='flex flex-col items-start justify-between self-stretch'>
<h3 className="text-h3 font-regular">{project?.title}</h3>
<a className="text-blue-400 font-regular text-body4" target='_blank' rel="noreferrer" href={project?.website}>{project?.website?.replace(/(^\w+:|^)\/\//, '')}</a>
<div>
<span className="chip-small font-light text-body5 py-4 px-12 mr-8"> {project?.category.title}</span>
<span className="chip-small bg-warning-50 font-light text-body5 py-4 px-12"><MdLocalFireDepartment className='inline-block text-fire transform text-body4 align-middle' /> {project?.votes_count}</span>
</div>
</div>
<div className="flex-shrink-0 hidden md:flex ml-auto gap-16">
<Button color='primary' size='md' className=" my-16">Play <BsJoystick /></Button>
{isWalletConnected ?
<Button onClick={onTip} size='md' className="border border-warning-100 bg-warning-50 hover:bg-warning-50 active:bg-warning-100 my-16">Tip <MdLocalFireDepartment className='text-fire' /></Button>
:
<Button onClick={onConnectWallet} size='md' className="border border-gray-200 bg-gray-100 hover:bg-gray-50 active:bg-gray-100 my-16">Connect Wallet to Vote</Button>
}
</div>
</div>
<p className="mt-40 text-body4 leading-normal">{project?.description}</p>
<div className="md:hidden">
<Button color='primary' size='md' fullWidth className="w-full mt-24 mb-16">Play <BsJoystick /></Button>
{isWalletConnected ?
<Button size='md' fullWidth className="bg-yellow-100 hover:bg-yellow-50 mb-24" onClick={onTip}>Vote <MdLocalFireDepartment className='text-fire' /></Button>
:
<Button size='md' fullWidth className="bg-gray-200 hover:bg-gray-100 mb-24" onClick={onConnectWallet}><AiFillThunderbolt className='inline-block text-thunder transform scale-125' /> Connect Wallet to Vote</Button>
}
</div>
<div className="mt-40">
<h3 className="text-h5 font-bold mb-16">Screenshots</h3>
<div className="grid grid-cols-2 gap-12 justify-items-center md:gap-24">
<div className="w-full relative pt-[56%]">
<div className="absolute top-0 left-0 w-full h-full object-cover bg-gray-300 rounded-xl"></div>
</div>
<div className="w-full relative pt-[56%]">
<div className="absolute top-0 left-0 w-full h-full object-cover bg-gray-300 rounded-xl"></div>
</div>
<div className="w-full relative pt-[56%]">
<div className="absolute top-0 left-0 w-full h-full object-cover bg-gray-300 rounded-xl"></div>
</div>
<div className="w-full relative pt-[56%]">
<div className="absolute top-0 left-0 w-full h-full object-cover bg-gray-300 rounded-xl"></div>
</div>
</div>
</div>
<hr className="my-40" />
<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" onClick={onClaim}>Claim 🖐</Button>
</div>
</div>
</motion.div>
)
}

View File

@@ -1,27 +0,0 @@
import { gql } from "@apollo/client";
import { Project } from "src/utils/interfaces";
export const PROJECT_BY_ID_QUERY = gql`
query Project($projectId: Int!) {
getProject(id: $projectId) {
id
cover_image
thumbnail_image
title
website
votes_count
category {
title
id
}
}
}
`;
export interface PROJECT_BY_ID_RES {
getProject: Project;
}
export interface PROJECT_BY_ID_VARS {
projectId: number;
}

View File

@@ -1,17 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import TipCard from './TipCard';
import { ModalsDecorator } from '.storybook/helpers'
export default {
title: 'Tip/Tip Card',
component: TipCard,
decorators: [ModalsDecorator]
} as ComponentMeta<typeof TipCard>;
const Template: ComponentStory<typeof TipCard> = (args) => <TipCard {...args} />;
export const Default = Template.bind({});

View File

@@ -1,148 +0,0 @@
import { motion } from 'framer-motion'
import React, { useState } from 'react';
import { AiFillThunderbolt } from 'react-icons/ai'
import { IoClose } from 'react-icons/io5'
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer';
import { useAppDispatch, useAppSelector } from '../../utils/hooks';
import { gql, useQuery, useMutation } from "@apollo/client";
import useWindowSize from "react-use/lib/useWindowSize";
import Confetti from "react-confetti";
const defaultOptions = [
{ text: '10 sat', value: 10 },
{ text: '100 sats', value: 100 },
{ text: '1k sats', value: 1000 },
]
enum PaymentStatus {
DEFAULT,
FETCHING_PAYMENT_DETAILS,
PAID,
AWAITING_PAYMENT,
PAYMENT_CONFIRMED,
NOT_PAID,
CANCELED
}
const VOTE = gql`
mutation Mutation($projectId: Int!, $amountInSat: Int!) {
vote(project_id: $projectId, amount_in_sat: $amountInSat) {
id
amount_in_sat
payment_request
payment_hash
paid
}
}
`;
const CONFIRM_VOTE = gql`
mutation Mutation($paymentRequest: String!, $preimage: String!) {
confirmVote(payment_request: $paymentRequest, preimage: $preimage) {
id
amount_in_sat
payment_request
payment_hash
paid
}
}
`;
export default function TipCard({ onClose, direction, ...props }: ModalCard) {
const { width, height } = useWindowSize()
const { isWalletConnected, webln } = useAppSelector(state => ({
isWalletConnected: state.wallet.isConnected,
webln: state.wallet.provider,
}));
const dispatch = useAppDispatch();
const [selectedOption, setSelectedOption] = useState(10);
const [voteAmount, setVoteAmount] = useState<number>(10);
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.DEFAULT);
const [vote, { data }] = useMutation(VOTE, {
onCompleted: (votingData) => {
setPaymentStatus(PaymentStatus.AWAITING_PAYMENT);
webln.sendPayment(votingData.vote.payment_request).then((res: any) => {
console.log("waiting for payment", res);
confirmVote({variables: { paymentRequest: votingData.vote.payment_request, preimage: res.preimage }});
setPaymentStatus(PaymentStatus.PAID);
})
.catch((err: any) => {
console.log(err);
setPaymentStatus(PaymentStatus.NOT_PAID);
});
}
});
const [confirmVote, { data: confirmedVoteData }] = useMutation(CONFIRM_VOTE, {
onCompleted: (votingData) => {
setPaymentStatus(PaymentStatus.PAYMENT_CONFIRMED);
}
});
const onChangeInput = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedOption(-1);
setVoteAmount(Number(event.target.value));
};
const onSelectOption = (idx: number) => {
setSelectedOption(idx);
setVoteAmount(defaultOptions[idx].value);
}
const requestPayment = () => {
setPaymentStatus(PaymentStatus.FETCHING_PAYMENT_DETAILS);
vote({variables: { "amountInSat": voteAmount, "projectId": parseInt("1") }});
}
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[343px] p-24 rounded-xl relative"
>
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
<h2 className='text-h5 font-bold'>Upvote Project</h2>
<div className="mt-32 ">
<label className="block text-gray-700 text-body4 mb-2 ">
Enter Amount
</label>
<div className="input-wrapper">
<input
className="input-field"
value={voteAmount} onChange={onChangeInput}
type="number"
placeholder="e.g 5 sats" />
{/* <IoCopy className='input-icon' /> */}
</div>
<div className="flex mt-16 justify-between">
{defaultOptions.map((option, idx) =>
<button
key={idx}
className={`btn border px-12 rounded-md py-8 text-body ${idx === selectedOption && "border-primary-500 bg-primary-100 hover:bg-primary-100 text-primary-600"}`}
onClick={() => onSelectOption(idx)}
>
{option.text}<AiFillThunderbolt className='inline-block text-thunder' />
</button>
)}
</div>
<p className="text-body6 mt-12 text-gray-500">1 sat = 1 vote</p>
{paymentStatus === PaymentStatus.FETCHING_PAYMENT_DETAILS && <p className="text-body6 mt-12 text-gray-500">Please wait while we the fetch payment details.</p>}
{paymentStatus === PaymentStatus.NOT_PAID && <p className="text-body6 mt-12 text-red-500">You did not confirm the payment. Please try again.</p>}
{paymentStatus === PaymentStatus.PAID && <p className="text-body6 mt-12 text-green-500">The invoice was paid! Please wait while we confirm it.</p>}
{paymentStatus === PaymentStatus.AWAITING_PAYMENT && <p className="text-body6 mt-12 text-yellow-500">Please confirm the payment in the prompt...</p>}
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <p className="text-body6 mt-12 text-green-500">Imagine confetti here</p>}
<button className="btn btn-primary w-full mt-32" onClick={requestPayment}>
Upvote
</button>
</div>
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <Confetti width={width} height={height} />}
</motion.div>
)
}