From 034b3317aaf3082e3ebbbb5d7e72ac7e1da59119 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Thu, 6 Oct 2022 13:03:13 +0300 Subject: [PATCH] feat: project details modal --- .../Categories/Categories.stories.tsx | 15 + .../Components/Categories/Categories.tsx | 68 +++++ .../Categories/allCategories.graphql | 7 + .../ProjectCardMini/ProjectCardMini.tsx | 4 +- .../pages/ExplorePage/ExplorePage.tsx | 2 + .../pages/ExplorePage/explorePage.graphql | 2 +- .../Claim_CopySignatureCard.stories.tsx | 17 ++ .../ClaimProject/Claim_CopySignatureCard.tsx | 61 ++++ .../Claim_FundWithdrawCard.stories.tsx | 17 ++ .../ClaimProject/Claim_FundWithdrawCard.tsx | 38 +++ .../Claim_GenerateSignatureCard.stories.tsx | 16 + .../Claim_GenerateSignatureCard.tsx | 59 ++++ .../Claim_SubmittedCard.stories.tsx | 17 ++ .../ClaimProject/Claim_SubmittedCard.tsx | 39 +++ .../pages/ProjectPage/ClaimProject/index.tsx | 7 + .../Components/AboutCard/AboutCard.tsx | 130 +++++++++ .../CapabilitiesCard/CapabilitiesCard.tsx | 26 ++ .../Components/LinksCard/LinksCard.tsx | 78 +++++ .../Components/MakersCard/MakersCard.tsx | 50 ++++ .../OpenRolesCard/OpenRolesCard.tsx | 25 ++ .../SimilarProjectsCard.stories.tsx | 20 ++ .../SimilarProjectsCard.tsx | 38 +++ .../ProjectDetailsCard/ProjectDetails.graphql | 31 ++ .../ProjectDetailsCard.Skeleton.tsx | 79 +++++ .../ProjectDetailsCard.stories.tsx | 25 ++ .../ProjectDetailsCard/ProjectDetailsCard.tsx | 275 ++++++++++++++++++ .../ProjectPage/ProjectDetailsCard/index.tsx | 7 + .../VoteButton/VoteButton.stories.tsx | 17 ++ .../ProjectPage/VoteButton/VoteButton.tsx | 157 ++++++++++ .../VoteButton/vote-button-style.module.css | 77 +++++ .../ProjectPage/VoteCard/VoteCard.stories.tsx | 17 ++ .../pages/ProjectPage/VoteCard/VoteCard.tsx | 126 ++++++++ .../pages/ProjectPage/VoteCard/index.tsx | 8 + .../pages/ProjectPage/styles.module.scss | 30 ++ .../Projects/types/project.interfaces.ts | 2 +- src/graphql/index.tsx | 124 +++++--- src/utils/apollo.ts | 4 - 37 files changed, 1674 insertions(+), 41 deletions(-) create mode 100644 src/features/Projects/Components/Categories/Categories.stories.tsx create mode 100644 src/features/Projects/Components/Categories/Categories.tsx create mode 100644 src/features/Projects/Components/Categories/allCategories.graphql create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/Claim_CopySignatureCard.stories.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/Claim_CopySignatureCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard.stories.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard.stories.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/Claim_SubmittedCard.stories.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/Claim_SubmittedCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ClaimProject/index.tsx create mode 100644 src/features/Projects/pages/ProjectPage/Components/AboutCard/AboutCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/Components/CapabilitiesCard/CapabilitiesCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/Components/LinksCard/LinksCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/Components/MakersCard/MakersCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/Components/OpenRolesCard/OpenRolesCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/Components/SimilarProjectsCard/SimilarProjectsCard.stories.tsx create mode 100644 src/features/Projects/pages/ProjectPage/Components/SimilarProjectsCard/SimilarProjectsCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetails.graphql create mode 100644 src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.Skeleton.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.stories.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/ProjectDetailsCard/index.tsx create mode 100644 src/features/Projects/pages/ProjectPage/VoteButton/VoteButton.stories.tsx create mode 100644 src/features/Projects/pages/ProjectPage/VoteButton/VoteButton.tsx create mode 100644 src/features/Projects/pages/ProjectPage/VoteButton/vote-button-style.module.css create mode 100644 src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.stories.tsx create mode 100644 src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx create mode 100644 src/features/Projects/pages/ProjectPage/VoteCard/index.tsx create mode 100644 src/features/Projects/pages/ProjectPage/styles.module.scss diff --git a/src/features/Projects/Components/Categories/Categories.stories.tsx b/src/features/Projects/Components/Categories/Categories.stories.tsx new file mode 100644 index 0000000..2c94d9d --- /dev/null +++ b/src/features/Projects/Components/Categories/Categories.stories.tsx @@ -0,0 +1,15 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import Categories from './Categories'; + + +export default { + title: 'Projects/Explore Page/Categories', + component: Categories, + +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + diff --git a/src/features/Projects/Components/Categories/Categories.tsx b/src/features/Projects/Components/Categories/Categories.tsx new file mode 100644 index 0000000..334a8b3 --- /dev/null +++ b/src/features/Projects/Components/Categories/Categories.tsx @@ -0,0 +1,68 @@ + +import { useNavigate } from 'react-router-dom'; +import { useAllCategoriesQuery } from 'src/graphql'; +import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; +import { useCarousel } from 'src/utils/hooks'; + +const colors = [ + '#FDF2F8', + '#F5F3FF', + '#FEFCE8', + '#F0FDF4', + '#EFF6FF', + '#FFFBEB', + '#FEF2F2', + '#FDF2F8', + '#FFF7ED', + '#F1F5F9' +] + +export default function Categories() { + + const { viewportRef, scrollSlides, canScrollNext, canScrollPrev, isClickAllowed } = useCarousel({ + align: 'start', slidesToScroll: 2, + containScroll: "trimSnaps", + }) + const { data, loading } = useAllCategoriesQuery(); + const navigate = useNavigate(); + + + if (loading || !data) + return
+ {Array(5).fill(0).map((_, idx) => +
+ category +
+ )} +
+ + + return ( + +
+
+
+ {data?.categoryList?.filter(c => c !== null).map((category, idx) => + + )} +
+
+ + +
+ + ) + +} diff --git a/src/features/Projects/Components/Categories/allCategories.graphql b/src/features/Projects/Components/Categories/allCategories.graphql new file mode 100644 index 0000000..4f738ae --- /dev/null +++ b/src/features/Projects/Components/Categories/allCategories.graphql @@ -0,0 +1,7 @@ +query AllCategories { + categoryList { + id + name + icon + } +} diff --git a/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.tsx b/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.tsx index 72f0ac3..22d1a8a 100644 --- a/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.tsx +++ b/src/features/Projects/Components/ProjectCardMini/ProjectCardMini.tsx @@ -20,10 +20,10 @@ export default function ProjectCardMini({ project, onClick }: Props) { tabIndex={0} role='button' > - {project?.project + {project?.title
-

{project?.category}

+

{project?.title}

{/*

{project?.category.title}

*/} {/* {numberFormatter(project?.votes_count)} */}
diff --git a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx index 13e0ec6..c86d0fc 100644 --- a/src/features/Projects/pages/ExplorePage/ExplorePage.tsx +++ b/src/features/Projects/pages/ExplorePage/ExplorePage.tsx @@ -4,6 +4,7 @@ import { useExplorePageQuery } from 'src/graphql'; import HeaderImage from './HeaderImage/HeaderImage'; import ProjectsGrid from './ProjectsGrid/ProjectsGrid'; import { Helmet } from "react-helmet"; +import Categories from '../../Components/Categories/Categories'; export default function ExplorePage() { @@ -38,6 +39,7 @@ export default function ExplorePage() { img={data?.getCategory.cover_image!} apps_count={data?.getCategory.apps_count!} /> */} +
; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + diff --git a/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_CopySignatureCard.tsx b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_CopySignatureCard.tsx new file mode 100644 index 0000000..4715c16 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_CopySignatureCard.tsx @@ -0,0 +1,61 @@ +import { motion } from 'framer-motion' +import { Direction, replaceModal } from 'src/redux/features/modals.slice'; +import { useAppDispatch, useAppSelector } from 'src/utils/hooks'; +import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer' +import CopyToClipboard from 'src/Components/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({ + Modal: 'Claim_SubmittedCard', + direction: Direction.NEXT + })) + }, [dispatch]) + + return ( + + +

Claim this project

+
+ +
+

+ Good job! Now paste this on the webpage + www.projectname.com/ +

+
+ + + +
+
+ +
+ +
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard.stories.tsx b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard.stories.tsx new file mode 100644 index 0000000..1470dfb --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard.stories.tsx @@ -0,0 +1,17 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import Claim_FundWithdrawCard from './Claim_FundWithdrawCard'; + +import { ModalsDecorator } from 'src/utils/storybook/decorators'; + +export default { + title: 'Projects/Claim/Fund Withdraw Card', + component: Claim_FundWithdrawCard, + + decorators: [ModalsDecorator] +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + diff --git a/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard.tsx b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard.tsx new file mode 100644 index 0000000..690b042 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_FundWithdrawCard.tsx @@ -0,0 +1,38 @@ +import { motion } from 'framer-motion' +// import { useAppDispatch } from '../../utils/hooks'; +import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer' + +export default function Claim_FundWithdrawCard({ onClose, direction, ...props }: ModalCard) { + + //const dispatch = useAppDispatch(); + + + return ( + +
+ +
+

+ 2,220 sats +

+

+ 2.78$ +

+
+ + +
+ +
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard.stories.tsx b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard.stories.tsx new file mode 100644 index 0000000..03a8249 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard.stories.tsx @@ -0,0 +1,16 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import Claim_GenerateSignatureCard from './Claim_GenerateSignatureCard'; + +import { ModalsDecorator } from 'src/utils/storybook/decorators'; + +export default { + title: 'Projects/Claim/Generate Signature Card', + component: Claim_GenerateSignatureCard, + decorators: [ModalsDecorator] +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + diff --git a/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard.tsx b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard.tsx new file mode 100644 index 0000000..81a131f --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_GenerateSignatureCard.tsx @@ -0,0 +1,59 @@ +import { motion } from 'framer-motion' +import { Direction, replaceModal } from 'src/redux/features/modals.slice'; +import { useAppDispatch, useAppSelector } from 'src/utils/hooks'; +import { ModalCard, modalCardVariants } from 'src/Components/Modals/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({ + Modal: 'Claim_CopySignatureCard', + direction: Direction.NEXT + })) + }, [dispatch]) + + useEffect(() => { + + // const timeout = setTimeout(handleNext, 3000) + // return () => clearTimeout(timeout) + }, [handleNext]) + + //const onCopy = () => { + // // Copy to Clipboard + // setTimeout(handleNext, 2000) + //} + + return ( + + +

Claim this project

+
+ +
+

+ To claim ownership of {projectName} 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 +

+
+ +
+ +
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_SubmittedCard.stories.tsx b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_SubmittedCard.stories.tsx new file mode 100644 index 0000000..1503522 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_SubmittedCard.stories.tsx @@ -0,0 +1,17 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import Claim_SubmittedCard from './Claim_SubmittedCard'; + +import { ModalsDecorator } from 'src/utils/storybook/decorators'; + +export default { + title: 'Projects/Claim/Submitted Card', + component: Claim_SubmittedCard, + + decorators: [ModalsDecorator] +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + diff --git a/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_SubmittedCard.tsx b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_SubmittedCard.tsx new file mode 100644 index 0000000..a4444c2 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ClaimProject/Claim_SubmittedCard.tsx @@ -0,0 +1,39 @@ +import { motion } from 'framer-motion' +import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer' +import { IoClose } from 'react-icons/io5'; + +export default function Claim_SubmittedCard({ onClose, direction, ...props }: ModalCard) { + + + return ( + + + +

Submitted For Review

+
+ success +
+

+ Great work! your claim to Application Name has been submitted for review. +
+ Check back soon to see if it was successful. +

+ + + +
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/ClaimProject/index.tsx b/src/features/Projects/pages/ProjectPage/ClaimProject/index.tsx new file mode 100644 index 0000000..cd94347 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ClaimProject/index.tsx @@ -0,0 +1,7 @@ + +import { lazyModal } from "src/utils/helperFunctions"; + +export const { LazyComponent: Claim_CopySignatureCard } = lazyModal(() => import('./Claim_CopySignatureCard')) +export const { LazyComponent: Claim_FundWithdrawCard } = lazyModal(() => import('./Claim_FundWithdrawCard')) +export const { LazyComponent: Claim_GenerateSignatureCard } = lazyModal(() => import('./Claim_GenerateSignatureCard')) +export const { LazyComponent: Claim_SubmittedCard } = lazyModal(() => import('./Claim_SubmittedCard')) \ No newline at end of file diff --git a/src/features/Projects/pages/ProjectPage/Components/AboutCard/AboutCard.tsx b/src/features/Projects/pages/ProjectPage/Components/AboutCard/AboutCard.tsx new file mode 100644 index 0000000..d77904c --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/Components/AboutCard/AboutCard.tsx @@ -0,0 +1,130 @@ +import linkifyHtml from 'linkifyjs/lib/linkify-html' +import { useState } from 'react' +import { MdLocalFireDepartment } from 'react-icons/md' +import Button from 'src/Components/Button/Button' +import Card from 'src/Components/Card/Card' +import Lightbox from 'src/Components/Lightbox/Lightbox' +import { ProjectDetailsQuery, ProjectLaunchStatusEnum, ProjectPermissionEnum, } from 'src/graphql' +import { openModal } from 'src/redux/features/modals.slice' +import { setVoteAmount } from 'src/redux/features/vote.slice' +import { numberFormatter } from 'src/utils/helperFunctions' +import { useAppDispatch } from 'src/utils/hooks' +import { createRoute } from 'src/utils/routing' +import LinksCard from '../LinksCard/LinksCard' + +interface Props { + project: Pick +} + + +export default function AboutCard({ project }: Props) { + + const dispatch = useAppDispatch(); + + const [screenshotsOpen, setScreenshotsOpen] = useState(-1); + + const onVote = (votes?: number) => { + dispatch(setVoteAmount(votes ?? 10)); + dispatch(openModal({ + Modal: 'VoteCard', props: { + projectId: project.id, + title: project.title, + initVotes: votes + } + })) + } + + + const canEdit = project.permissions.includes(ProjectPermissionEnum.UpdateInfo); + + return ( + + {/* Cover Image */} +
+ +
+ {project.launch_status === ProjectLaunchStatusEnum.Launched && `🚀 Launched`} + {project.launch_status === ProjectLaunchStatusEnum.Wip && `🔧 WIP`} +
+
+ +
+
+
+ {/* Title & Basic Info */} +
+
+ {canEdit && } + +
+
+

{project.title}

+

{project.tagline}

+
+ {project.category.icon} {project.category.title} +
+
+ +
+
+ +
+ + {/* About */} +
+
+ +
+ {project.screenshots.length > 0 && <> +
+
+ {project.screenshots.slice(0, 4).map((screenshot, idx) =>
setScreenshotsOpen(idx)} + > + +
)} +
+
+ setScreenshotsOpen(-1)} + /> + } +
+
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/Components/CapabilitiesCard/CapabilitiesCard.tsx b/src/features/Projects/pages/ProjectPage/Components/CapabilitiesCard/CapabilitiesCard.tsx new file mode 100644 index 0000000..1f03fee --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/Components/CapabilitiesCard/CapabilitiesCard.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import Badge from 'src/Components/Badge/Badge' +import Card from 'src/Components/Card/Card' +import { ProjectDetailsQuery } from 'src/graphql' + + +interface Props { + capabilities: ProjectDetailsQuery['getProject']['capabilities'] +} + + +export default function CapabilitiesCard({ capabilities }: Props) { + return ( + +

🦾 Capabilities

+
+ {capabilities.length === 0 && <> +

No capabilities added

+ } +
+ {capabilities.map(cap => {cap.icon} {cap.title})} +
+
+
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/Components/LinksCard/LinksCard.tsx b/src/features/Projects/pages/ProjectPage/Components/LinksCard/LinksCard.tsx new file mode 100644 index 0000000..a370c6e --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/Components/LinksCard/LinksCard.tsx @@ -0,0 +1,78 @@ +import Card from 'src/Components/Card/Card' +import { Project } from 'src/graphql' +import { FaDiscord, } from 'react-icons/fa'; +import { FiGithub, FiGlobe, FiTwitter } from 'react-icons/fi'; +import CopyToClipboard from 'react-copy-to-clipboard'; +import { NotificationsService, } from 'src/services' + +interface Props { + links: Pick +} + +export default function LinksCard({ links }: Props) { + const linksList = [ + { + value: links.discord, + text: links.discord, + icon: FaDiscord, + colors: "bg-violet-100 text-violet-900", + }, + { + value: links.website, + text: links.website?.replace(/(^\w+:|^)\/\//, '').replace(/\/$/, ""), + icon: FiGlobe, + colors: "bg-gray-100 text-gray-900", + url: links.website + }, + { + value: links.twitter, + text: links.twitter, + icon: FiTwitter, + colors: "bg-blue-100 text-blue-500", + url: links.twitter + }, + { + value: links.github, + text: links.github, + icon: FiGithub, + colors: "bg-pink-100 text-pink-600", + url: links.github + }, + ]; + + return ( + +

🔗 Links

+
+ {linksList.length === 0 && <> +

No links added

+ } +
+ {linksList.filter(link => !!link.value).map((link, idx) => + (link.url ? + + + : + NotificationsService.info(" Copied to clipboard", { icon: "📋" })} + > + + + ))} +
+
+
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/Components/MakersCard/MakersCard.tsx b/src/features/Projects/pages/ProjectPage/Components/MakersCard/MakersCard.tsx new file mode 100644 index 0000000..29c86f0 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/Components/MakersCard/MakersCard.tsx @@ -0,0 +1,50 @@ +import { Link } from 'react-router-dom' +import Badge from 'src/Components/Badge/Badge' +import Card from 'src/Components/Card/Card' +import Avatar from 'src/features/Profiles/Components/Avatar/Avatar' +import { sortMembersByRole } from 'src/features/Projects/utils/helperFunctions' +import { ProjectDetailsQuery } from 'src/graphql' +import { createRoute } from 'src/utils/routing' + + +interface Props { + members: ProjectDetailsQuery['getProject']['members'] + recruit_roles: ProjectDetailsQuery['getProject']['recruit_roles'] + +} + + +export default function MakersCard({ members, recruit_roles }: Props) { + return ( + +

👾 Makers

+
+
+ {members.length === 0 &&

Not listed

} + {sortMembersByRole(members).map(m => +
+ +
+

{m.user.name}

+

{m.user.jobTitle}

+
+
} + /> + )} +
+
+

Open roles

+
+ {recruit_roles.length === 0 && <> +

No open roles for now

+ } +
+ {recruit_roles.map(role => {role.icon} {role.title})} +
+
+
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/Components/OpenRolesCard/OpenRolesCard.tsx b/src/features/Projects/pages/ProjectPage/Components/OpenRolesCard/OpenRolesCard.tsx new file mode 100644 index 0000000..95d22b2 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/Components/OpenRolesCard/OpenRolesCard.tsx @@ -0,0 +1,25 @@ +import Badge from 'src/Components/Badge/Badge' +import Card from 'src/Components/Card/Card' +import { ProjectDetailsQuery } from 'src/graphql' + + +interface Props { + recruit_roles: ProjectDetailsQuery['getProject']['recruit_roles'] +} + + +export default function OpenRolesCard({ recruit_roles }: Props) { + return ( + +

👀 Open roles

+
+ {recruit_roles.length === 0 && <> +

No open roles for now

+ } +
+ {recruit_roles.map(role => {role.icon} {role.title})} +
+
+
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/Components/SimilarProjectsCard/SimilarProjectsCard.stories.tsx b/src/features/Projects/pages/ProjectPage/Components/SimilarProjectsCard/SimilarProjectsCard.stories.tsx new file mode 100644 index 0000000..0b7da66 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/Components/SimilarProjectsCard/SimilarProjectsCard.stories.tsx @@ -0,0 +1,20 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import SimilarProjectsCard from './SimilarProjectsCard'; + +export default { + title: 'Projects/Project Page/Similar Projects Card', + component: SimilarProjectsCard, + argTypes: { + backgroundColor: { control: 'color' }, + }, +} as ComponentMeta; + + +const Template: ComponentStory = (args) =>
+ +export const Default = Template.bind({}); +Default.args = { +} + + diff --git a/src/features/Projects/pages/ProjectPage/Components/SimilarProjectsCard/SimilarProjectsCard.tsx b/src/features/Projects/pages/ProjectPage/Components/SimilarProjectsCard/SimilarProjectsCard.tsx new file mode 100644 index 0000000..3f2ddd9 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/Components/SimilarProjectsCard/SimilarProjectsCard.tsx @@ -0,0 +1,38 @@ + +import { Link } from 'react-router-dom' +import Card from 'src/Components/Card/Card' +import Avatar from 'src/features/Profiles/Components/Avatar/Avatar' +import { User, useSimilarProjectsQuery } from 'src/graphql' +import { createRoute } from 'src/utils/routing' + +interface Props { + id: number +} + +export default function SimilarProjectsCard({ id }: Props) { + + const query = useSimilarProjectsQuery({ variables: { projectId: id } }) + + if (query.loading) return null; + + if (query.data?.similarProjects.length === 0) return null; + + return ( + +

🚀 Similar projects

+
    + {query.data?.similarProjects.map(project => { + return +
  • + +
    +

    {project.title}

    +

    {project.category.icon} {project.category.title}

    +
    +
  • + + })} +
+
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetails.graphql b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetails.graphql new file mode 100644 index 0000000..574a1d0 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetails.graphql @@ -0,0 +1,31 @@ +query ProjectDetails($projectsId: String) { + projects(id: $projectsId) { + id + title + dead + createdAt + companyName + category + categoryList { + name + } + description + discord + endDate + twitter + updatedAt + watchers + website + yearFounded + telegram + subcategory + stars + repository + openSource + logo + linkedIn + license + language + forks + } +} diff --git a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.Skeleton.tsx b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.Skeleton.tsx new file mode 100644 index 0000000..d96ef63 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.Skeleton.tsx @@ -0,0 +1,79 @@ +import { motion } from 'framer-motion' +import { MdClose, } from 'react-icons/md'; +import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'; +import Skeleton from 'react-loading-skeleton'; +import Badge from 'src/Components/Badge/Badge'; +import { useMediaQuery } from 'src/utils/hooks'; +import { MEDIA_QUERIES } from 'src/utils/theme'; +import Button from 'src/Components/Button/Button'; + + +interface Props extends ModalCard { +} + +export default function ProjectDetailsCardSkeleton({ onClose, direction, ...props }: Props) { + + + + const isMdScreen = useMediaQuery(MEDIA_QUERIES.isMedium) + + + return ( + +
+ + +
+
+
+
+ +
+
+

+

+
+ +
+
+ +
+ +
+
+

+ + + + +

+ +
+ + +
+ +
+
+ { + Array(4).fill(0).map((_, idx) =>
+
+
) + } +
+
+
+
+
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.stories.tsx b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.stories.tsx new file mode 100644 index 0000000..b2514d7 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.stories.tsx @@ -0,0 +1,25 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import ProjectDetailsCard from './ProjectDetailsCard'; +import ProjectDetailsCardSkeleton from './ProjectDetailsCard.Skeleton'; +import { ModalsDecorator } from 'src/utils/storybook/decorators'; + +export default { + title: 'Projects/Project Page/Project Details Modal', + component: ProjectDetailsCard, + decorators: [ModalsDecorator], +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); +Default.args = { + projectId: 1, + isPageModal: true +} + + + +const LoadingTemplate: ComponentStory = (args) => ; +export const LoadingState = LoadingTemplate.bind({ + isPageModal: true +}) \ No newline at end of file diff --git a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.tsx b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.tsx new file mode 100644 index 0000000..a7eef1c --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.tsx @@ -0,0 +1,275 @@ +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 { Link } from 'react-router-dom'; +import { createRoute } from 'src/utils/routing'; +import { IoMdClose } from 'react-icons/io'; + + +interface Props extends ModalCard { + projectId: string; +} + +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: { + projectsId: projectId!, + }, + onCompleted: data => { + dispatch(setProject((data.projects?.[0] as any) ?? null)) + }, + onError: () => { + dispatch(setProject(null)); + }, + skip: !Boolean(projectId) + }); + + + + const closeModal = () => { + props.onClose?.(); + } + + + + + if (error) + return
+
+ +
+
+ + if (loading) + return ; + + + const project = data?.projects?.[0]; + + if (!project) return

404

+ + 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 ( +
+ {/* Cover Image */} +
+ +
+
+ {project.launch_status === ProjectLaunchStatusEnum.Launched && `🚀 Launched`} + {project.launch_status === ProjectLaunchStatusEnum.Wip && `🔧 WIP`} +
+
+ {project.permissions.includes(ProjectPermissionEnum.UpdateInfo) && + props.onClose?.()} to={createRoute({ type: "edit-project", id: project.id })}>} + +
+
+
+
+ + {/* Title & Basic Info */} +
+
+ +
+
+

{project.title}

+

{project.tagline}

+
+ {project.category.icon} {project.category.title} +
+
+
+ {/* */} + {/* */} + {/* */} + {/* {isWalletConnected ? + : + + } */} + +
+
+ + + {/* About */} +
+

About

+
+ + {/* Links */} +
+ {links.filter(link => !!link.value).map((link, idx) => + (link.url ? + + + : + NotificationsService.info(" Copied to clipboard", { icon: "📋" })} + > + + + ))} +
+
+ {project.screenshots.length > 0 && <> +
+
+ {project.screenshots.slice(0, 4).map((screenshot, idx) =>
setScreenshotsOpen(idx)} + > + +
)} +
+
+ setScreenshotsOpen(-1)} + /> + } + + {project.capabilities.length > 0 && +
+

CAPABILITIES

+
+ {project.capabilities.map(cap => {cap.icon} {cap.title})} +
+
} + + {project.members.length > 0 && +
+

MAKERS

+
+ {sortMembersByRole(project.members).map(m => +
+ +
+

{m.user.name}

+

{m.user.jobTitle}

+
+
} + /> + )} +
+
} + + + + {/*
+

Are you the creator of this project

+ +
*/} +
+
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/index.tsx b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/index.tsx new file mode 100644 index 0000000..e4526bf --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/index.tsx @@ -0,0 +1,7 @@ + +import ProjectDetailsCardSkeleton from './ProjectDetailsCard.Skeleton' +import { lazyModal } from 'src/utils/helperFunctions'; + + + +export const { LazyComponent: ProjectDetailsCard, preload: projectDetailsCardPreload } = lazyModal(() => import('./ProjectDetailsCard'), ProjectDetailsCardSkeleton) diff --git a/src/features/Projects/pages/ProjectPage/VoteButton/VoteButton.stories.tsx b/src/features/Projects/pages/ProjectPage/VoteButton/VoteButton.stories.tsx new file mode 100644 index 0000000..4961f7d --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/VoteButton/VoteButton.stories.tsx @@ -0,0 +1,17 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import VoteButton from './VoteButton'; +import { centerDecorator } from 'src/utils/storybook/decorators'; + +export default { + title: 'Projects/Project Page/Vote Button', + component: VoteButton, + decorators: [ + centerDecorator + ] +} as ComponentMeta; + +const Template: ComponentStory = (args) => { }} />; + +export const Default = Template.bind({}); + diff --git a/src/features/Projects/pages/ProjectPage/VoteButton/VoteButton.tsx b/src/features/Projects/pages/ProjectPage/VoteButton/VoteButton.tsx new file mode 100644 index 0000000..5bb71dc --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/VoteButton/VoteButton.tsx @@ -0,0 +1,157 @@ +import { MdLocalFireDepartment } from 'react-icons/md' +import Button from 'src/Components/Button/Button' +import { useAppSelector, usePressHolder } from 'src/utils/hooks' +import _throttle from 'lodash.throttle' +import { ComponentProps, useRef, useState } from 'react' +import styles from './vote-button-style.module.css' +import { random, randomItem } from 'src/utils/helperFunctions' + + +interface Particle { + id: string, + offsetX: number, + color: '#ff6a00' | '#ff7717' | '#ff6217' | '#ff8217' | '#ff5717' + animation: 'fly-spark-1' | 'fly-spark-2', + animationSpeed: number, + scale: number +} + +type Props = { + onVote: (Vote: number) => void +} & Omit, 'children'> + +export default function VoteButton({ onVote = () => { }, ...props }: Props) { + const [voteCnt, setVoteCnt] = useState(0) + const voteCntRef = useRef(0); + const [sparks, setSparks] = useState([]); + const [wasActive, setWasActive] = useState(false); + + const isMobileScreen = useAppSelector(s => s.ui.isMobileScreen) + + + const { onPressDown, onPressUp } = usePressHolder(_throttle(() => { + const _incStep = (Math.ceil((voteCnt + 1) / 10) + 1) ** 2 * 10; + setVoteCnt(s => { + const newValue = s + _incStep; + voteCntRef.current = newValue; + return newValue; + }) + + const newSpark = { + id: Math.random().toString(), + offsetX: random(1, 99), + animation: randomItem(styles.fly_spark_1, styles.fly_spark_1) as any, + animationSpeed: randomItem(1, 1.5, 2), + color: randomItem('#ff6a00', '#ff7717', '#ff6217', '#ff8217', '#ff5717'), + scale: random(1.2, 2.2) + } as const; + + // if on mobile screen, reduce number of sparks particles to 60% + if (!isMobileScreen || Math.random() > .4) { + setSparks(oldSparks => [...oldSparks, newSpark]) + setTimeout(() => { + setSparks(s => { + return s.filter(spark => spark.id !== newSpark.id) + }) + + }, newSpark.animationSpeed * 1000) + } + + }, 100), 100); + + const handlePressDown = () => { + setWasActive(true); + onPressDown(); + } + + const handlePressUp = (event?: any) => { + if (!wasActive) return; + + setWasActive(false); + if (event?.preventDefault) event.preventDefault(); + onPressUp(); + if (voteCnt === 0) + onVote(10); + else + setTimeout(() => { + setSparks([]); + onVote(voteCntRef.current); + setVoteCnt(0); + voteCntRef.current = 0; + }, 500) + } + + return ( + + + ) +} diff --git a/src/features/Projects/pages/ProjectPage/VoteButton/vote-button-style.module.css b/src/features/Projects/pages/ProjectPage/VoteButton/vote-button-style.module.css new file mode 100644 index 0000000..b827e6a --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/VoteButton/vote-button-style.module.css @@ -0,0 +1,77 @@ +.vote_button { + --scale: 0; + transition: background-color 1s; + background-color: hsl(25, 100%, max(calc((95 - var(--scale) / 4) * 1%), 63%)); +} + +.vote_counter { + position: absolute; + left: 50%; + bottom: 110%; + color: hsl(25, 100%, 50%); + font-weight: bold; + font-size: 21px; + will-change: transform; + opacity: min(calc(var(--scale) * 1), 1); + transform: translate(-50%, max(calc(-1px * var(--scale) / 10), -30px)) + scale(calc(1 + min(var(--scale) / 150, 2))); + text-shadow: 0 0 4px hsl(25, 100%, 50%); +} + +.spark { + position: absolute; + bottom: 46%; + left: calc(var(--offsetX) * 1%); + transform: scale(var(--scale)); + opacity: 0; + will-change: transform; + + animation-name: fly-spark-1; + animation-duration: calc(var(--animationSpeed) * 1s); + animation-timing-function: linear; + animation-iteration-count: 1; + animation-fill-mode: forwards; + filter: drop-shadow(0 0 4px); +} + +@keyframes fly_spark_1 { + 0% { + transform: translate(0, 0) scale(var(--scale)); + opacity: 1; + } + + 33% { + transform: translate(12px, -70px) scale(var(--scale)); + } + + 66% { + transform: translate(0, -140px) scale(var(--scale)); + opacity: 0.6; + } + + 100% { + transform: translate(6px, -200px) scale(var(--scale)); + opacity: 0; + } +} + +@keyframes fly_spark_2 { + 0% { + transform: translate(0, 0) scale(var(--scale)); + opacity: 1; + } + + 50% { + transform: translate(-10px, -80px) scale(var(--scale)); + } + + 80% { + transform: translate(-4px, -140px) scale(var(--scale)); + opacity: 0.6; + } + + 100% { + transform: translate(-6px, -160px) scale(var(--scale)); + opacity: 0; + } +} diff --git a/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.stories.tsx b/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.stories.tsx new file mode 100644 index 0000000..1d6e13f --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.stories.tsx @@ -0,0 +1,17 @@ +import { ComponentStory, ComponentMeta } from '@storybook/react'; + +import VoteCard from './VoteCard'; + +import { ModalsDecorator } from 'src/utils/storybook/decorators'; + +export default { + title: 'Projects/Project Page/Vote Card', + component: VoteCard, + + decorators: [ModalsDecorator] +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Default = Template.bind({}); + diff --git a/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx b/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx new file mode 100644 index 0000000..cd805b9 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/VoteCard/VoteCard.tsx @@ -0,0 +1,126 @@ +import { motion } from 'framer-motion' +import React, { FormEvent, useState } from 'react'; +import { AiFillThunderbolt } from 'react-icons/ai' +import { IoClose } from 'react-icons/io5' +import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer'; +import { PaymentStatus, useVote } from 'src/utils/hooks'; +import Confetti from "react-confetti"; +import { useWindowSize } from '@react-hookz/web'; +import { Vote_Item_Type } from 'src/graphql'; +import IconButton from 'src/Components/IconButton/IconButton'; + +const defaultOptions = [ + { text: '100 sat', value: 100 }, + { text: '1k sat', value: 1000 }, + { text: '10k sats', value: 10000 }, +] + + +interface Props extends ModalCard { + projectId: number; + title?: string; + initVotes?: number; +} + +export default function VoteCard({ onClose, direction, projectId, initVotes, ...props }: Props) { + const { width, height } = useWindowSize() + + + + const [selectedOption, setSelectedOption] = useState(10); + const [voteAmount, setVoteAmount] = useState(initVotes ?? 10); + const { vote, paymentStatus } = useVote({ + itemId: projectId, + itemType: Vote_Item_Type.Project + }) + + + + const onChangeInput = (event: React.ChangeEvent) => { + setSelectedOption(-1); + setVoteAmount(Number(event.target.value)); + }; + + const onSelectOption = (idx: number) => { + setSelectedOption(idx); + setVoteAmount(defaultOptions[idx].value); + } + + const requestPayment = (e: FormEvent) => { + e.preventDefault(); + vote(voteAmount, { + onSuccess: () => { + setTimeout(() => { + onClose?.(); + }, 4000); + }, + onError: () => { + setTimeout(() => { + onClose?.(); + }, 4000); + } + }); + } + + return ( + +
+

Vote for {props.title ?? "project"}

+ + + +
+
+ +
+ +

+ Sats +

+
+
+ {defaultOptions.map((option, idx) => + + )} +
+

1 sat = 1 vote

+

Where do these sats go?
Claimed project votes go directly towards the maker's. Unclaimed project votes go towards BOLT.FUN's community pool.

+ {paymentStatus === PaymentStatus.FETCHING_PAYMENT_DETAILS &&

Please wait while we the fetch payment details.

} + {paymentStatus === PaymentStatus.NOT_PAID &&

You did not confirm the payment. Please try again.

} + {paymentStatus === PaymentStatus.PAID &&

The invoice was paid! Please wait while we confirm it.

} + {paymentStatus === PaymentStatus.AWAITING_PAYMENT &&

Waiting for your payment...

} + {paymentStatus === PaymentStatus.PAYMENT_CONFIRMED &&

Thanks for your vote

} + +
+ {paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && } +
+ ) +} diff --git a/src/features/Projects/pages/ProjectPage/VoteCard/index.tsx b/src/features/Projects/pages/ProjectPage/VoteCard/index.tsx new file mode 100644 index 0000000..b8ce0e3 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/VoteCard/index.tsx @@ -0,0 +1,8 @@ + +import { lazyModal } from 'src/utils/helperFunctions'; + +export const { LazyComponent: VoteCard } = lazyModal(() => import('./VoteCard')) + + + + diff --git a/src/features/Projects/pages/ProjectPage/styles.module.scss b/src/features/Projects/pages/ProjectPage/styles.module.scss new file mode 100644 index 0000000..f663255 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/styles.module.scss @@ -0,0 +1,30 @@ +.grid { + display: grid; + grid-template-columns: 100%; + gap: 24px; + grid-template-areas: "main"; + + > aside:first-of-type { + display: flex; + flex-direction: column; + gap: 24px; + grid-area: aside1; + } + > main { + display: flex; + flex-direction: column; + gap: 24px; + grid-area: main; + } + > aside:last-of-type { + display: flex; + flex-direction: column; + gap: 24px; + grid-area: aside2; + } + + @media screen and (min-width: 768px) { + grid-template-columns: 1fr 2fr 1fr; + grid-template-areas: "aside1 main aside2"; + } +} diff --git a/src/features/Projects/types/project.interfaces.ts b/src/features/Projects/types/project.interfaces.ts index 82532ff..68d8ba0 100644 --- a/src/features/Projects/types/project.interfaces.ts +++ b/src/features/Projects/types/project.interfaces.ts @@ -25,5 +25,5 @@ export interface ProjectCategory { } -export type ProjectCard = Pick +export type ProjectCard = Pick diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index f56dfb2..d285445 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -158,12 +158,12 @@ export type QueryProjectsArgs = { linkedIn: InputMaybe; logo: InputMaybe>>; openSource: InputMaybe; - project: InputMaybe; repository: InputMaybe; stars: InputMaybe; status: InputMaybe; subcategory: InputMaybe>>; telegram: InputMaybe; + title: InputMaybe; twitter: InputMaybe; updatedAt: InputMaybe; watchers: InputMaybe; @@ -300,12 +300,12 @@ export type Projects = { linkedIn: Maybe; logo: Maybe>>; openSource: Maybe; - project: Maybe; repository: Maybe; stars: Maybe; status: Maybe; subcategory: Maybe>>; telegram: Maybe; + title: Maybe; twitter: Maybe; updatedAt: Maybe; watchers: Maybe; @@ -327,14 +327,10 @@ export type Tags = { projectProductFromFeatured: Maybe>>; }; -export type SearchProjectsQueryVariables = Exact<{ - category: InputMaybe; - pageSize: InputMaybe; - page: InputMaybe; -}>; +export type AllCategoriesQueryVariables = Exact<{ [key: string]: never; }>; -export type SearchProjectsQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'projects', id: string | null, project: string | null, category: string | null, subcategory: Array | null, logo: Array | null, yearFounded: number | null } | null> | null }; +export type AllCategoriesQuery = { __typename?: 'Query', categoryList: Array<{ __typename?: 'categoryList', id: string | null, name: string | null, icon: string | null } | null> | null }; export type ExplorePageQueryVariables = Exact<{ page: InputMaybe; @@ -342,56 +338,57 @@ export type ExplorePageQueryVariables = Exact<{ }>; -export type ExplorePageQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'projects', id: string | null, project: string | null, category: string | null, logo: Array | null, yearFounded: number | null, websiteFunctionalLightningRelated: string | null, companyName: string | null, website: string | null, description: string | null, repository: string | null, status: string | null, dead: boolean | null, twitter: string | null, linkedIn: string | null, telegram: string | null, language: string | null, updatedAt: string | null, createdAt: string | null, discord: string | null, stars: number | null } | null> | null }; +export type ExplorePageQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'projects', id: string | null, title: string | null, category: string | null, logo: Array | null, yearFounded: number | null, websiteFunctionalLightningRelated: string | null, companyName: string | null, website: string | null, description: string | null, repository: string | null, status: string | null, dead: boolean | null, twitter: string | null, linkedIn: string | null, telegram: string | null, language: string | null, updatedAt: string | null, createdAt: string | null, discord: string | null, stars: number | null } | null> | null }; + +export type ProjectDetailsQueryVariables = Exact<{ + projectsId: InputMaybe; +}>; -export const SearchProjectsDocument = gql` - query SearchProjects($category: String, $pageSize: JSON, $page: JSON) { - projects(category: $category, _page_size: $pageSize, _page: $page) { +export type ProjectDetailsQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'projects', id: string | null, title: string | null, dead: boolean | null, createdAt: string | null, companyName: string | null, category: string | null, description: string | null, discord: string | null, endDate: string | null, twitter: string | null, updatedAt: string | null, watchers: number | null, website: string | null, yearFounded: number | null, telegram: string | null, subcategory: Array | null, stars: number | null, repository: string | null, openSource: string | null, logo: Array | null, linkedIn: string | null, license: string | null, language: string | null, forks: number | null, categoryList: Array<{ __typename?: 'categoryList', name: string | null } | null> | null } | null> | null }; + + +export const AllCategoriesDocument = gql` + query AllCategories { + categoryList { id - project - category - subcategory - logo - yearFounded + name + icon } } `; /** - * __useSearchProjectsQuery__ + * __useAllCategoriesQuery__ * - * To run a query within a React component, call `useSearchProjectsQuery` and pass it any options that fit your needs. - * When your component renders, `useSearchProjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useAllCategoriesQuery` and pass it any options that fit your needs. + * When your component renders, `useAllCategoriesQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const { data, loading, error } = useSearchProjectsQuery({ + * const { data, loading, error } = useAllCategoriesQuery({ * variables: { - * category: // value for 'category' - * pageSize: // value for 'pageSize' - * page: // value for 'page' * }, * }); */ -export function useSearchProjectsQuery(baseOptions?: Apollo.QueryHookOptions) { +export function useAllCategoriesQuery(baseOptions?: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(SearchProjectsDocument, options); + return Apollo.useQuery(AllCategoriesDocument, options); } -export function useSearchProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useAllCategoriesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(SearchProjectsDocument, options); + return Apollo.useLazyQuery(AllCategoriesDocument, options); } -export type SearchProjectsQueryHookResult = ReturnType; -export type SearchProjectsLazyQueryHookResult = ReturnType; -export type SearchProjectsQueryResult = Apollo.QueryResult; +export type AllCategoriesQueryHookResult = ReturnType; +export type AllCategoriesLazyQueryHookResult = ReturnType; +export type AllCategoriesQueryResult = Apollo.QueryResult; export const ExplorePageDocument = gql` query ExplorePage($page: JSON, $pageSize: JSON) { projects(_page: $page, _page_size: $pageSize) { id - project + title category logo yearFounded @@ -441,4 +438,65 @@ export function useExplorePageLazyQuery(baseOptions?: Apollo.LazyQueryHookOption } export type ExplorePageQueryHookResult = ReturnType; export type ExplorePageLazyQueryHookResult = ReturnType; -export type ExplorePageQueryResult = Apollo.QueryResult; \ No newline at end of file +export type ExplorePageQueryResult = Apollo.QueryResult; +export const ProjectDetailsDocument = gql` + query ProjectDetails($projectsId: String) { + projects(id: $projectsId) { + id + title + dead + createdAt + companyName + category + categoryList { + name + } + description + discord + endDate + twitter + updatedAt + watchers + website + yearFounded + telegram + subcategory + stars + repository + openSource + logo + linkedIn + license + language + forks + } +} + `; + +/** + * __useProjectDetailsQuery__ + * + * To run a query within a React component, call `useProjectDetailsQuery` and pass it any options that fit your needs. + * When your component renders, `useProjectDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useProjectDetailsQuery({ + * variables: { + * projectsId: // value for 'projectsId' + * }, + * }); + */ +export function useProjectDetailsQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(ProjectDetailsDocument, options); + } +export function useProjectDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(ProjectDetailsDocument, options); + } +export type ProjectDetailsQueryHookResult = ReturnType; +export type ProjectDetailsLazyQueryHookResult = ReturnType; +export type ProjectDetailsQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/src/utils/apollo.ts b/src/utils/apollo.ts index b2d7fc3..acaba74 100644 --- a/src/utils/apollo.ts +++ b/src/utils/apollo.ts @@ -8,10 +8,6 @@ let apiClientUri = "https://api.baseql.com/airtable/graphql/app7wOLbDNm617R18"; const httpLink = new HttpLink({ uri: apiClientUri, - credentials: "include", - headers: { - 'Authorization': 'Bearer NWU1YTNhNGItZWQ1ZC00NWQyLTk4M2ItNWRhZGViMGYxMjQ4' - }, }); const errorLink = onError(({ graphQLErrors, networkError, response }) => {