diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index 7120730..8310857 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -633,6 +633,7 @@ export interface NexusGenFieldTypes { searchProjects: NexusGenRootTypes['Project'][]; // [Project!]! searchUsers: NexusGenRootTypes['User'][]; // [User!]! similarMakers: NexusGenRootTypes['User'][]; // [User!]! + similarProjects: NexusGenRootTypes['Project'][]; // [Project!]! tournamentParticipationInfo: NexusGenRootTypes['ParticipationInfo'] | null; // ParticipationInfo } Question: { // field return type @@ -1021,6 +1022,7 @@ export interface NexusGenFieldTypeNames { searchProjects: 'Project' searchUsers: 'User' similarMakers: 'User' + similarProjects: 'Project' tournamentParticipationInfo: 'ParticipationInfo' } Question: { // field return type name @@ -1286,7 +1288,8 @@ export interface NexusGenArgTypes { type: NexusGenEnums['POST_TYPE']; // POST_TYPE! } getProject: { // args - id: number; // Int! + id?: number | null; // Int + tag?: string | null; // String } getProjectsInTournament: { // args roleId?: number | null; // Int @@ -1325,6 +1328,9 @@ export interface NexusGenArgTypes { similarMakers: { // args id: number; // Int! } + similarProjects: { // args + id: number; // Int! + } tournamentParticipationInfo: { // args tournamentId: number; // Int! } diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index 2481ecb..6f64e81 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -342,7 +342,7 @@ type Query { getMakersInTournament(openToConnect: Boolean, roleId: Int, search: String, skip: Int = 0, take: Int = 10, tournamentId: Int!): TournamentMakersResponse! getMyDrafts(type: POST_TYPE!): [Post!]! getPostById(id: Int!, type: POST_TYPE!): Post! - getProject(id: Int!): Project! + getProject(id: Int, tag: String): Project! getProjectsInTournament(roleId: Int, search: String, skip: Int = 0, take: Int = 10, tournamentId: Int!): TournamentProjectsResponse! getTournamentById(id: Int!): Tournament! getTournamentToRegister: [Tournament!]! @@ -357,6 +357,7 @@ type Query { searchProjects(search: String!, skip: Int = 0, take: Int = 50): [Project!]! searchUsers(value: String!): [User!]! similarMakers(id: Int!): [User!]! + similarProjects(id: Int!): [Project!]! tournamentParticipationInfo(tournamentId: Int!): ParticipationInfo } diff --git a/api/functions/graphql/types/project.js b/api/functions/graphql/types/project.js index ef7bf46..4346760 100644 --- a/api/functions/graphql/types/project.js +++ b/api/functions/graphql/types/project.js @@ -273,9 +273,11 @@ const getProject = extendType({ t.nonNull.field('getProject', { type: "Project", args: { - id: nonNull(intArg()) + id: intArg(), + tag: stringArg(), }, - resolve(_, { id }) { + resolve(_, { id, tag }) { + if (tag) return prisma.project.findFirst({ where: { hashtag: tag } }) return prisma.project.findUnique({ where: { id } }) @@ -439,6 +441,35 @@ const getLnurlDetailsForProject = extendType({ } }) +const similarProjects = extendType({ + type: "Query", + definition(t) { + t.nonNull.list.nonNull.field('similarProjects', { + type: "Project", + args: { + id: nonNull(intArg()) + }, + async resolve(parent, { id }, ctx) { + const currentProject = await prisma.project.findUnique({ where: { id }, select: { category_id: true } }) + + return prisma.project.findMany({ + where: { + AND: { + id: { + not: id + }, + category_id: { + equals: currentProject.category_id + } + } + }, + take: 5, + }) + } + }) + } +}) + const TeamMemberInput = inputObjectType({ name: 'TeamMemberInput', definition(t) { @@ -1081,6 +1112,7 @@ module.exports = { getLnurlDetailsForProject, getAllCapabilities, checkValidProjectHashtag, + similarProjects, // Mutations createProject, diff --git a/prisma/seed/index.js b/prisma/seed/index.js index 2c51ee4..80b378e 100644 --- a/prisma/seed/index.js +++ b/prisma/seed/index.js @@ -69,7 +69,9 @@ async function main() { // await migrateOldImages(); - await createCapabilities(); + // await createCapabilities(); + + // await createHashtags(); } async function migrateOldImages() { @@ -238,7 +240,7 @@ async function migrateOldImages() { /** * Tournament **/ - const tournaments = await prisma.tournament.findMany({ + const tournaments = await prisma.tournament.findMany({ select: { id: true, thumbnail_image: true, @@ -263,7 +265,7 @@ async function migrateOldImages() { /** * TournamentPrize **/ - const tournamentPrizes = await prisma.tournamentPrize.findMany({ + const tournamentPrizes = await prisma.tournamentPrize.findMany({ select: { id: true, image: true, @@ -508,6 +510,24 @@ async function createCapabilities() { }) } +async function createHashtags() { + console.log("Creating Hashtags for projects"); + const projects = await prisma.project.findMany({ select: { title: true, id: true } }); + for (let i = 0; i < projects.length; i++) { + const project = projects[i]; + await prisma.project.update({ + where: { id: project.id }, + data: { + hashtag: project.title.toLowerCase() + .trim() + .replace(/[^\w\s-]/g, '') + .replace(/[\s_-]+/g, '_') + .replace(/^-+|-+$/g, '') + } + }) + } +} + main() .catch((e) => { diff --git a/src/App.tsx b/src/App.tsx index 7de6cf1..158ca8f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,7 +14,6 @@ import { Loadable, PAGES_ROUTES } from "./utils/routing"; import ListProjectPage from "./features/Projects/pages/ListProjectPage/ListProjectPage"; - // Pages const FeedPage = Loadable(React.lazy(() => import( /* webpackChunkName: "feed_page" */ "./features/Posts/pages/FeedPage/FeedPage"))) const PostDetailsPage = Loadable(React.lazy(() => import( /* webpackChunkName: "post_details_page" */ "./features/Posts/pages/PostDetailsPage/PostDetailsPage"))) @@ -23,6 +22,7 @@ const CreatePostPage = Loadable(React.lazy(() => import( /* webpackChunkName: " const HottestPage = Loadable(React.lazy(() => import( /* webpackChunkName: "hottest_page" */ "src/features/Projects/pages/HottestPage/HottestPage"))) const CategoryPage = Loadable(React.lazy(() => import( /* webpackChunkName: "category_page" */ "src/features/Projects/pages/CategoryPage/CategoryPage"))) const ExplorePage = Loadable(React.lazy(() => import( /* webpackChunkName: "explore_page" */ "src/features/Projects/pages/ExplorePage"))) +const ProjectPage = Loadable(React.lazy(() => import( /* webpackChunkName: "explore_page" */ "src/features/Projects/pages/ProjectPage/ProjectPage"))) const HackathonsPage = Loadable(React.lazy(() => import( /* webpackChunkName: "hackathons_page" */ "./features/Hackathons/pages/HackathonsPage/HackathonsPage"))) @@ -100,6 +100,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> } /> diff --git a/src/Components/Badge/Badge.tsx b/src/Components/Badge/Badge.tsx index 3102562..881782e 100644 --- a/src/Components/Badge/Badge.tsx +++ b/src/Components/Badge/Badge.tsx @@ -23,7 +23,7 @@ const badgrColor: UnionToObjectKeys = { } const badgeSize: UnionToObjectKeys = { - sm: "px-8 py-4 text-body6", + sm: "px-12 py-4 text-body5", md: "px-16 py-8 text-body4", lg: "px-24 py-12 text-body3" } diff --git a/src/features/Profiles/pages/ProfilePage/StoriesCard/StoriesCard.tsx b/src/features/Profiles/pages/ProfilePage/StoriesCard/StoriesCard.tsx index 8dbe808..d7c114a 100644 --- a/src/features/Profiles/pages/ProfilePage/StoriesCard/StoriesCard.tsx +++ b/src/features/Profiles/pages/ProfilePage/StoriesCard/StoriesCard.tsx @@ -21,11 +21,12 @@ interface Props { tags: Array> } > + onlyMd?: boolean } -export default function StoriesCard({ stories, isOwner }: Props) { +export default function StoriesCard({ stories, isOwner, onlyMd }: Props) { return ( - +

Stories ({stories.length})

{stories.length > 0 &&
    @@ -51,12 +52,12 @@ export default function StoriesCard({ stories, isOwner }: Props) {
} {stories.length === 0 &&
-

+

😐 No Stories Added Yet

-

+ {/*

The maker have not written any stories yet -

+

*/} {isOwner && }, [clickSubmit, isDirty, isLoading, isUpdating, props.currentTab, props.onNext]) + if (navigateToCreatedProject) return + return ( -
+ {wrapLink(
{img ? :
@@ -124,7 +129,8 @@ export default function SaveChangesCard(props: Props) {

{name || "Product preview"}

{

{tagline || "Provide some more details."}

}
-
+
, isUpdating ? createRoute({ type: "project", tag: hashtag }) : undefined)} +
{/*

{trimText(profileQuery.data.profile.bio, 120)}

*/}
diff --git a/src/features/Projects/pages/ListProjectPage/Components/TeamMembersInput/MemberRow.tsx b/src/features/Projects/pages/ListProjectPage/Components/TeamMembersInput/MemberRow.tsx index 2a64cd7..bb6ab66 100644 --- a/src/features/Projects/pages/ListProjectPage/Components/TeamMembersInput/MemberRow.tsx +++ b/src/features/Projects/pages/ListProjectPage/Components/TeamMembersInput/MemberRow.tsx @@ -1,8 +1,5 @@ import { Menu, MenuButton, MenuItem } from '@szhsin/react-menu'; -import { ComponentProps } from 'react' -import { NestedValue } from 'react-hook-form' import { FaChevronDown, FaRegTrashAlt, } from 'react-icons/fa'; -import UsersInput from 'src/Components/Inputs/UsersInput/UsersInput' import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'; import { Team_Member_Role } from 'src/graphql'; import { Value } from './TeamMembersInput' 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..7a9e2b5 --- /dev/null +++ b/src/features/Projects/pages/ProjectPage/Components/MakersCard/MakersCard.tsx @@ -0,0 +1,49 @@ +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 { 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

} + {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 index 66ea49e..d867b65 100644 --- a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetails.graphql +++ b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetails.graphql @@ -1,5 +1,5 @@ -query ProjectDetails($projectId: Int!) { - getProject(id: $projectId) { +query ProjectDetails($projectId: Int, $projectTag: String) { + getProject(id: $projectId, tag: $projectTag) { id title tagline @@ -56,3 +56,17 @@ query ProjectDetails($projectId: Int!) { } } } + +query SimilarProjects($projectId: Int!) { + similarProjects(id: $projectId) { + id + title + hashtag + thumbnail_image + category { + id + icon + title + } + } +} diff --git a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.Skeleton.tsx b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.Skeleton.tsx index 2501813..d96ef63 100644 --- a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.Skeleton.tsx +++ b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.Skeleton.tsx @@ -32,7 +32,7 @@ export default function ProjectDetailsCardSkeleton({ onClose, direction, ...prop
-
+
@@ -46,26 +46,24 @@ export default function ProjectDetailsCardSkeleton({ onClose, direction, ...prop
- {/* */} - {/* */} - {/* */} - {/* {isWalletConnected ? - : - - } */}
-

+

-
+
+ + +
+ +
{ Array(4).fill(0).map((_, idx) =>
@@ -74,10 +72,7 @@ export default function ProjectDetailsCardSkeleton({ onClose, direction, ...prop }
-
-
- -
+
) diff --git a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.tsx b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.tsx index 879360b..6963b36 100644 --- a/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.tsx +++ b/src/features/Projects/pages/ProjectPage/ProjectDetailsCard/ProjectDetailsCard.tsx @@ -41,7 +41,7 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P const isMdScreen = useMediaQuery(MEDIA_QUERIES.isMedium) const { data, loading, error } = useProjectDetailsQuery({ - variables: { projectId: projectId! }, + variables: { projectId: projectId!, projectTag: null }, onCompleted: data => { dispatch(setProject(data.getProject)) }, @@ -51,10 +51,6 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P skip: !Boolean(projectId) }); - useEffect(() => { - return () => { - } - }, [dispatch]); const closeModal = () => { props.onClose?.(); @@ -73,9 +69,6 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P if (loading || !data?.getProject) return ; - const onConnectWallet = async () => { - Wallet_Service.connectWallet() - } const project = data.getProject; @@ -109,35 +102,17 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P }, ]; - - - const canEdit = project.permissions.includes(ProjectPermissionEnum.UpdateInfo); - const onVote = (votes?: number) => { dispatch(setVoteAmount(votes ?? 10)); dispatch(openModal({ Modal: 'VoteCard', props: { projectId: project.id, + title: project.title, initVotes: votes } })) } - - const onClaim = () => { - if (!isWalletConnected) { - dispatch(scheduleModal({ - Modal: 'Claim_GenerateSignatureCard', - })) - dispatch(openModal({ - Modal: 'Login_ScanningWalletCard' - })) - } else - dispatch(openModal({ - Modal: 'Claim_GenerateSignatureCard', - })) - } - return (
-
- {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 })}>} - +
+
+ {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 })}>} + +
@@ -252,7 +229,7 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P {project.capabilities.map(cap => {cap.icon} {cap.title})}
} -
+ {project.members.length > 0 &&

MAKERS

@@ -272,6 +249,9 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P )}
} + + + {/*

Are you the creator of this project