From 84d73942fa600f9ae1c4fbb408999a3dffa92847 Mon Sep 17 00:00:00 2001 From: Beau Hawkinson <72956780+Twonarly1@users.noreply.github.com> Date: Fri, 2 May 2025 12:57:21 -0500 Subject: [PATCH 1/7] feat: implement a hover card for redesign on large devices --- .../organizations/[organizationSlug]/page.tsx | 20 ++++-- .../projects/[projectSlug]/page.tsx | 37 ++++++----- src/components/core/Breadcrumb/Breadcrumb.tsx | 65 ++++++++++++------- .../BreadrcumbTrigger/BreadcrumbTrigger.tsx | 41 ++++++++++++ src/components/core/HoverCard/HoverCard.tsx | 37 +++++++++++ src/components/core/index.ts | 2 + src/components/layout/Header/Header.tsx | 12 +++- src/lib/actions/getOrganizations.ts | 29 +++++++++ src/lib/actions/index.ts | 1 + 9 files changed, 195 insertions(+), 49 deletions(-) create mode 100644 src/components/core/BreadrcumbTrigger/BreadcrumbTrigger.tsx create mode 100644 src/components/core/HoverCard/HoverCard.tsx create mode 100644 src/lib/actions/getOrganizations.ts diff --git a/src/app/organizations/[organizationSlug]/page.tsx b/src/app/organizations/[organizationSlug]/page.tsx index 94f54291..8e6f0667 100644 --- a/src/app/organizations/[organizationSlug]/page.tsx +++ b/src/app/organizations/[organizationSlug]/page.tsx @@ -17,7 +17,7 @@ import { useOrganizationQuery, } from "generated/graphql"; import { Grid } from "generated/panda/jsx"; -import { getOrganization } from "lib/actions"; +import { getOrganization, getOrganizations } from "lib/actions"; import { app } from "lib/config"; import { MAX_NUMBER_OF_PROJECTS } from "lib/constants"; import { @@ -57,11 +57,13 @@ const OrganizationPage = async ({ params }: Props) => { if (!session) notFound(); - const [organization, isBasicTier, isTeamTier] = await Promise.all([ - getOrganization({ organizationSlug }), - enableBasicTierPrivilegesFlag(), - enableTeamTierPrivilegesFlag(), - ]); + const [organization, organizations, isBasicTier, isTeamTier] = + await Promise.all([ + getOrganization({ organizationSlug }), + getOrganizations(), + enableBasicTierPrivilegesFlag(), + enableTeamTierPrivilegesFlag(), + ]); if (!organization) notFound(); @@ -89,6 +91,12 @@ const OrganizationPage = async ({ params }: Props) => { }, { label: organization.name ?? organizationSlug, + subItems: organizations?.length + ? organizations.map((organization) => ({ + label: organization?.name!, + href: `/organizations/${organization!.slug}`, + })) + : undefined, }, ]; diff --git a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx index 2c0c0d8f..8fe30704 100644 --- a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx +++ b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx @@ -15,14 +15,12 @@ import { useProjectStatusesQuery, useStatusBreakdownQuery, } from "generated/graphql"; -import { getProject } from "lib/actions"; +import { getOrganization, getOrganizations, getProject } from "lib/actions"; import { app } from "lib/config"; import { getSdk } from "lib/graphql"; -import { getQueryClient, getSearchParams } from "lib/util"; +import { getQueryClient } from "lib/util"; import type { BreadcrumbRecord } from "components/core"; -import type { PostOrderBy } from "generated/graphql"; -import type { SearchParams } from "nuqs/server"; export const generateMetadata = async ({ params }: Props) => { const { organizationSlug, projectSlug } = await params; @@ -40,21 +38,23 @@ export const generateMetadata = async ({ params }: Props) => { interface Props { /** Project page params. */ params: Promise<{ organizationSlug: string; projectSlug: string }>; - /** Projects page search params. */ - searchParams: Promise; } /** * Project overview page. */ -const ProjectPage = async ({ params, searchParams }: Props) => { +const ProjectPage = async ({ params }: Props) => { const { organizationSlug, projectSlug } = await params; const session = await auth(); if (!session) notFound(); - const project = await getProject({ organizationSlug, projectSlug }); + const [project, organizations, organization] = await Promise.all([ + getProject({ organizationSlug, projectSlug }), + getOrganizations(), + getOrganization({ organizationSlug }), + ]); if (!project) notFound(); @@ -65,9 +65,6 @@ const ProjectPage = async ({ params, searchParams }: Props) => { organizationId: project.organization?.rowId!, }); - const { excludedStatuses, orderBy, search } = - await getSearchParams.parse(searchParams); - const queryClient = getQueryClient(); const breadcrumbs: BreadcrumbRecord[] = [ @@ -78,6 +75,12 @@ const ProjectPage = async ({ params, searchParams }: Props) => { { label: project.organization?.name ?? organizationSlug, href: `/organizations/${organizationSlug}`, + subItems: organizations?.length + ? organizations.map((organization) => ({ + label: organization!.name, + href: `/organizations/${organization!.slug}`, + })) + : undefined, }, { label: app.projectsPage.breadcrumb, @@ -85,6 +88,12 @@ const ProjectPage = async ({ params, searchParams }: Props) => { }, { label: project.name ?? projectSlug, + subItems: organization?.projects?.nodes?.length + ? organization?.projects?.nodes.map((project) => ({ + label: project!.name, + href: `/organizations/${organizationSlug}/projects/${project!.slug}`, + })) + : undefined, }, ]; @@ -103,16 +112,10 @@ const ProjectPage = async ({ params, searchParams }: Props) => { queryKey: useInfinitePostsQuery.getKey({ pageSize: 5, projectId: project.rowId, - excludedStatuses, - orderBy: orderBy ? (orderBy as PostOrderBy) : undefined, - search, }), queryFn: usePostsQuery.fetcher({ pageSize: 5, projectId: project.rowId, - excludedStatuses, - orderBy: orderBy ? (orderBy as PostOrderBy) : undefined, - search, }), initialPageParam: undefined, }), diff --git a/src/components/core/Breadcrumb/Breadcrumb.tsx b/src/components/core/Breadcrumb/Breadcrumb.tsx index 97ad3f57..58b54f18 100644 --- a/src/components/core/Breadcrumb/Breadcrumb.tsx +++ b/src/components/core/Breadcrumb/Breadcrumb.tsx @@ -1,16 +1,25 @@ "use client"; -import { Flex, Icon, Text } from "@omnidev/sigil"; +import { Button, Flex, Icon, Text } from "@omnidev/sigil"; import { LuChevronRight } from "react-icons/lu"; -import { Link } from "components/core"; +import { BreadcrumbTrigger, HoverCard, Link } from "components/core"; import { app } from "lib/config"; +interface SubItemRecord { + /** Label for the sub-item. */ + label: string; + /** URL path the sub-item navigates to. */ + href: `/${string}`; +} + export interface BreadcrumbRecord { /** Label for the breadcrumb. */ label: string; /** URL path the breadcrumb navigates to. */ href?: `/${string}`; + /** Sub-items for the breadcrumb. */ + subItems?: SubItemRecord[]; } interface Props { @@ -29,7 +38,7 @@ const Breadcrumb = ({ breadcrumbs }: Props) => ( - {breadcrumbs.map(({ label, href }, index) => { + {breadcrumbs.map(({ label, href, subItems }, index) => { const isLastItem = breadcrumbs.length - 1 === index; return ( @@ -43,27 +52,37 @@ const Breadcrumb = ({ breadcrumbs }: Props) => ( mx={1.5} /> - {href ? ( - - - ... - - + + + } > - {label} - - + + {subItems?.map(({ label, href }) => ( + + + + ))} + + + ) : ( + href && ( + + + + ) + ) ) : ( {label} )} diff --git a/src/components/core/BreadrcumbTrigger/BreadcrumbTrigger.tsx b/src/components/core/BreadrcumbTrigger/BreadcrumbTrigger.tsx new file mode 100644 index 00000000..f34cec3d --- /dev/null +++ b/src/components/core/BreadrcumbTrigger/BreadcrumbTrigger.tsx @@ -0,0 +1,41 @@ +import { Icon, Text } from "@omnidev/sigil"; +import { FiChevronDown } from "react-icons/fi"; + +const sharedTextStyles = { + cursor: "pointer", + color: { + base: "foreground.subtle", + _hover: "foreground.default", + }, +}; + +interface Props { + /** Label for the breadcrumb. */ + label: string; + /** Whether the breadcrumb is the last item. */ + isLastItem: boolean; + /** Icon for the breadcrumb. */ + icon?: boolean; +} + +const BreadcrumbTrigger = ({ label, isLastItem, icon }: Props) => ( + <> + + ... + + + + {label} + + {icon && } + + +); + +export default BreadcrumbTrigger; diff --git a/src/components/core/HoverCard/HoverCard.tsx b/src/components/core/HoverCard/HoverCard.tsx new file mode 100644 index 00000000..ebc6ca61 --- /dev/null +++ b/src/components/core/HoverCard/HoverCard.tsx @@ -0,0 +1,37 @@ +import { css } from "@omnidev/sigil"; +import { + HoverCardContent, + HoverCardPositioner, + HoverCardRoot, + HoverCardTrigger, +} from "@ark-ui/react/hover-card"; +import { Portal } from "@ark-ui/react/portal"; + +import type { PropsWithChildren, ReactNode } from "react"; +import type { HoverCardRootProps } from "@ark-ui/react/hover-card"; + +interface Props extends HoverCardRootProps, PropsWithChildren { + trigger: ReactNode; +} + +const HoverCard = ({ children, trigger, ...rest }: Props) => ( + + {trigger} + + + + {children} + + + + +); + +export default HoverCard; diff --git a/src/components/core/index.ts b/src/components/core/index.ts index 9758a96a..ce09da16 100644 --- a/src/components/core/index.ts +++ b/src/components/core/index.ts @@ -2,6 +2,7 @@ export { default as Breadcrumb, type BreadcrumbRecord, } from "./Breadcrumb/Breadcrumb"; +export { default as BreadcrumbTrigger } from "./BreadrcumbTrigger/BreadcrumbTrigger"; export { default as CallToAction, type ActionButton, @@ -12,6 +13,7 @@ export { default as DestructiveAction, type Props as DestructiveActionProps, } from "./DestructiveAction/DestructiveAction"; +export { default as HoverCard } from "./HoverCard/HoverCard"; export { default as Image } from "./Image/Image"; export { default as Link } from "./Link/Link"; export { default as LogoLink } from "./LogoLink/LogoLink"; diff --git a/src/components/layout/Header/Header.tsx b/src/components/layout/Header/Header.tsx index 7aae8f64..d930faff 100644 --- a/src/components/layout/Header/Header.tsx +++ b/src/components/layout/Header/Header.tsx @@ -1,16 +1,22 @@ "use client"; -import { Flex, HStack, Icon, css, sigil } from "@omnidev/sigil"; -import { Link as SigilLink } from "@omnidev/sigil"; +import { + Flex, + HStack, + Icon, + Link as SigilLink, + css, + sigil, +} from "@omnidev/sigil"; import { useQuery } from "@tanstack/react-query"; import { usePathname } from "next/navigation"; +import { LuExternalLink } from "react-icons/lu"; import { Link, LogoLink } from "components/core"; import { HeaderActions } from "components/layout"; import { app } from "lib/config"; import { useAuth } from "lib/hooks"; import { subscriptionOptions } from "lib/options"; -import { LuExternalLink } from "react-icons/lu"; /** * Layout header. diff --git a/src/lib/actions/getOrganizations.ts b/src/lib/actions/getOrganizations.ts new file mode 100644 index 00000000..16ecaaa6 --- /dev/null +++ b/src/lib/actions/getOrganizations.ts @@ -0,0 +1,29 @@ +import { cache } from "react"; + +import { getSdk } from "lib/graphql"; +import { getAuthSession } from "lib/util"; + +import { Role } from "generated/graphql"; + +/** + * Helper function to fetch an organization's details. Cached for deduping requests. + */ +const getOrganizations = cache(async () => { + const session = await getAuthSession(); + + if (!session) return null; + + const sdk = getSdk({ session }); + + const { organizations } = await sdk.Organizations({ + userId: session.user?.rowId!, + isMember: false, + excludeRoles: [Role.Member], + }); + + if (!organizations) return null; + + return organizations?.nodes; +}); + +export default getOrganizations; diff --git a/src/lib/actions/index.ts b/src/lib/actions/index.ts index bd41222f..7b478967 100644 --- a/src/lib/actions/index.ts +++ b/src/lib/actions/index.ts @@ -1,5 +1,6 @@ export { default as getCustomer } from "./getCustomer"; export { default as getOrganization } from "./getOrganization"; +export { default as getOrganizations } from "./getOrganizations"; export { default as getProduct } from "./getProduct"; export { default as getProject } from "./getProject"; export { default as getSubscription } from "./getSubscription"; From 8f7630bbb744fd2681f66af9315e7bf6e3e418db Mon Sep 17 00:00:00 2001 From: Beau Hawkinson <72956780+Twonarly1@users.noreply.github.com> Date: Tue, 6 May 2025 14:28:04 -0500 Subject: [PATCH 2/7] feature: refactor all the condensed breadcrumb routes into a drop and extract code for readability --- .../organizations/[organizationSlug]/page.tsx | 12 +- .../projects/[projectSlug]/page.tsx | 23 ++- src/components/core/Breadcrumb/Breadcrumb.tsx | 95 ------------ .../BreadrcumbTrigger/BreadcrumbTrigger.tsx | 41 ----- src/components/core/HoverCard/HoverCard.tsx | 37 ----- .../core/breadcrumb/Breadcrumb/Breadcrumb.tsx | 141 ++++++++++++++++++ .../BreadcrumbDropdown/BreadcrumbDropdown.tsx | 28 ++++ .../BreadrcumbTrigger/BreadcrumbTrigger.tsx | 34 +++++ src/components/core/breadcrumb/index.ts | 6 + src/components/core/index.ts | 7 +- .../AccountInformation/AccountInformation.tsx | 2 +- src/lib/actions/getOrganizations.ts | 2 +- 12 files changed, 227 insertions(+), 201 deletions(-) delete mode 100644 src/components/core/Breadcrumb/Breadcrumb.tsx delete mode 100644 src/components/core/BreadrcumbTrigger/BreadcrumbTrigger.tsx delete mode 100644 src/components/core/HoverCard/HoverCard.tsx create mode 100644 src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx create mode 100644 src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx create mode 100644 src/components/core/breadcrumb/BreadrcumbTrigger/BreadcrumbTrigger.tsx create mode 100644 src/components/core/breadcrumb/index.ts diff --git a/src/app/organizations/[organizationSlug]/page.tsx b/src/app/organizations/[organizationSlug]/page.tsx index 8e6f0667..bfcab98f 100644 --- a/src/app/organizations/[organizationSlug]/page.tsx +++ b/src/app/organizations/[organizationSlug]/page.tsx @@ -91,12 +91,12 @@ const OrganizationPage = async ({ params }: Props) => { }, { label: organization.name ?? organizationSlug, - subItems: organizations?.length - ? organizations.map((organization) => ({ - label: organization?.name!, - href: `/organizations/${organization!.slug}`, - })) - : undefined, + // subItems: organizations?.length + // ? organizations.map((organization) => ({ + // label: organization?.name!, + // href: `/organizations/${organization!.slug}`, + // })) + // : undefined, }, ]; diff --git a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx index 8fe30704..54235ea7 100644 --- a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx +++ b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx @@ -15,7 +15,7 @@ import { useProjectStatusesQuery, useStatusBreakdownQuery, } from "generated/graphql"; -import { getOrganization, getOrganizations, getProject } from "lib/actions"; +import { getOrganization, getProject } from "lib/actions"; import { app } from "lib/config"; import { getSdk } from "lib/graphql"; import { getQueryClient } from "lib/util"; @@ -50,13 +50,12 @@ const ProjectPage = async ({ params }: Props) => { if (!session) notFound(); - const [project, organizations, organization] = await Promise.all([ + const [project, organization] = await Promise.all([ getProject({ organizationSlug, projectSlug }), - getOrganizations(), getOrganization({ organizationSlug }), ]); - if (!project) notFound(); + if (!project || !organization) notFound(); const sdk = getSdk({ session }); @@ -75,12 +74,6 @@ const ProjectPage = async ({ params }: Props) => { { label: project.organization?.name ?? organizationSlug, href: `/organizations/${organizationSlug}`, - subItems: organizations?.length - ? organizations.map((organization) => ({ - label: organization!.name, - href: `/organizations/${organization!.slug}`, - })) - : undefined, }, { label: app.projectsPage.breadcrumb, @@ -89,10 +82,12 @@ const ProjectPage = async ({ params }: Props) => { { label: project.name ?? projectSlug, subItems: organization?.projects?.nodes?.length - ? organization?.projects?.nodes.map((project) => ({ - label: project!.name, - href: `/organizations/${organizationSlug}/projects/${project!.slug}`, - })) + ? organization?.projects?.nodes + .filter((p) => p?.slug !== projectSlug) + .map((project) => ({ + label: project!.name, + href: `/organizations/${organizationSlug}/projects/${project!.slug}`, + })) : undefined, }, ]; diff --git a/src/components/core/Breadcrumb/Breadcrumb.tsx b/src/components/core/Breadcrumb/Breadcrumb.tsx deleted file mode 100644 index 58b54f18..00000000 --- a/src/components/core/Breadcrumb/Breadcrumb.tsx +++ /dev/null @@ -1,95 +0,0 @@ -"use client"; - -import { Button, Flex, Icon, Text } from "@omnidev/sigil"; -import { LuChevronRight } from "react-icons/lu"; - -import { BreadcrumbTrigger, HoverCard, Link } from "components/core"; -import { app } from "lib/config"; - -interface SubItemRecord { - /** Label for the sub-item. */ - label: string; - /** URL path the sub-item navigates to. */ - href: `/${string}`; -} - -export interface BreadcrumbRecord { - /** Label for the breadcrumb. */ - label: string; - /** URL path the breadcrumb navigates to. */ - href?: `/${string}`; - /** Sub-items for the breadcrumb. */ - subItems?: SubItemRecord[]; -} - -interface Props { - /** Array of navigation breadcrumbs. */ - breadcrumbs: BreadcrumbRecord[]; -} - -/** - * Breadcrumb. - */ -const Breadcrumb = ({ breadcrumbs }: Props) => ( - - - - {app.breadcrumb} - - - - {breadcrumbs.map(({ label, href, subItems }, index) => { - const isLastItem = breadcrumbs.length - 1 === index; - - return ( - // biome-ignore lint/suspicious/noArrayIndexKey: index used in the key in case an organization and project have the same label - - - - {href || subItems ? ( - subItems?.length ? ( - - - - } - > - - {subItems?.map(({ label, href }) => ( - - - - ))} - - - ) : ( - href && ( - - - - ) - ) - ) : ( - {label} - )} - - ); - })} - -); - -export default Breadcrumb; diff --git a/src/components/core/BreadrcumbTrigger/BreadcrumbTrigger.tsx b/src/components/core/BreadrcumbTrigger/BreadcrumbTrigger.tsx deleted file mode 100644 index f34cec3d..00000000 --- a/src/components/core/BreadrcumbTrigger/BreadcrumbTrigger.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Icon, Text } from "@omnidev/sigil"; -import { FiChevronDown } from "react-icons/fi"; - -const sharedTextStyles = { - cursor: "pointer", - color: { - base: "foreground.subtle", - _hover: "foreground.default", - }, -}; - -interface Props { - /** Label for the breadcrumb. */ - label: string; - /** Whether the breadcrumb is the last item. */ - isLastItem: boolean; - /** Icon for the breadcrumb. */ - icon?: boolean; -} - -const BreadcrumbTrigger = ({ label, isLastItem, icon }: Props) => ( - <> - - ... - - - - {label} - - {icon && } - - -); - -export default BreadcrumbTrigger; diff --git a/src/components/core/HoverCard/HoverCard.tsx b/src/components/core/HoverCard/HoverCard.tsx deleted file mode 100644 index ebc6ca61..00000000 --- a/src/components/core/HoverCard/HoverCard.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { css } from "@omnidev/sigil"; -import { - HoverCardContent, - HoverCardPositioner, - HoverCardRoot, - HoverCardTrigger, -} from "@ark-ui/react/hover-card"; -import { Portal } from "@ark-ui/react/portal"; - -import type { PropsWithChildren, ReactNode } from "react"; -import type { HoverCardRootProps } from "@ark-ui/react/hover-card"; - -interface Props extends HoverCardRootProps, PropsWithChildren { - trigger: ReactNode; -} - -const HoverCard = ({ children, trigger, ...rest }: Props) => ( - - {trigger} - - - - {children} - - - - -); - -export default HoverCard; diff --git a/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx b/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx new file mode 100644 index 00000000..cd601f3d --- /dev/null +++ b/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx @@ -0,0 +1,141 @@ +"use client"; + +import { Flex, Icon, Text } from "@omnidev/sigil"; +import { LuChevronRight, LuEllipsis } from "react-icons/lu"; + +import { BreadcrumbDropdown, BreadcrumbTrigger, Link } from "components/core"; +import { app } from "lib/config"; + +const sharedIconStyles = { + color: "foreground.subtle", + h: 4, + w: 4, + mx: 1.5, +}; + +interface SubItemRecord { + /** Label for the sub-item. */ + label: string; + /** URL path the sub-item navigates to. */ + href: `/${string}`; +} + +export interface BreadcrumbRecord { + /** Label for the breadcrumb. */ + label: string; + /** URL path the breadcrumb navigates to. */ + href?: `/${string}`; + /** Sub-items for the breadcrumb. */ + subItems?: SubItemRecord[]; +} + +interface Props { + /** Array of navigation breadcrumbs. */ + breadcrumbs: BreadcrumbRecord[]; +} + +/** + * Breadcrumb. + */ +const Breadcrumb = ({ breadcrumbs }: Props) => { + const lastItem = breadcrumbs[breadcrumbs.length - 1]; + + return ( + + + + {app.breadcrumb} + + + + {/* Large breakpoint and above hidden */} + + + + {breadcrumbs.length > 1 && ( + + + + + } + breadcrumbs={breadcrumbs.slice(0, -1)} + /> + + + + )} + + {lastItem.subItems?.length ? ( + + + + } + breadcrumbs={lastItem.subItems} + /> + ) : ( + + )} + + + {/* Mobile hidden */} + + {breadcrumbs.map(({ label, href, subItems }, index) => { + const isLastItem = breadcrumbs.length - 1 === index; + + return ( + // biome-ignore lint/suspicious/noArrayIndexKey: index used in the key in case an organization and project have the same label + + + + {subItems?.length ? ( + + + + } + breadcrumbs={subItems} + /> + ) : href ? ( + + + + ) : ( + {label} + )} + + ); + })} + + + ); +}; + +export default Breadcrumb; diff --git a/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx b/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx new file mode 100644 index 00000000..53328dab --- /dev/null +++ b/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx @@ -0,0 +1,28 @@ +import { Menu, MenuItemGroup, MenuItem, Text } from "@omnidev/sigil"; + +import { Link } from "components/core"; + +import type { BreadcrumbRecord } from "components/core/breadcrumb"; +import type { MenuProps } from "@omnidev/sigil"; + +interface Props extends MenuProps { + /** Array of navigation breadcrumbs. */ + breadcrumbs: BreadcrumbRecord[]; +} + +/** + * Breadcrumb dropdown. + */ +const BreadcrumbDropdown = ({ breadcrumbs, trigger, ...rest }: Props) => ( + + + {breadcrumbs.map(({ label, href }) => ( + + {href ? {label} : {label}} + + ))} + + +); + +export default BreadcrumbDropdown; diff --git a/src/components/core/breadcrumb/BreadrcumbTrigger/BreadcrumbTrigger.tsx b/src/components/core/breadcrumb/BreadrcumbTrigger/BreadcrumbTrigger.tsx new file mode 100644 index 00000000..8d583d18 --- /dev/null +++ b/src/components/core/breadcrumb/BreadrcumbTrigger/BreadcrumbTrigger.tsx @@ -0,0 +1,34 @@ +import { Flex, Icon } from "@omnidev/sigil"; +import { FiChevronDown } from "react-icons/fi"; + +import { OverflowText } from "components/core"; + +interface Props { + /** Label for the breadcrumb. */ + label: string; + /** Whether the breadcrumb is the last item. */ + isLastItem: boolean; + /** Icon for the breadcrumb. */ + icon?: boolean; +} + +/** + * Breadcrumb trigger. + */ +const BreadcrumbTrigger = ({ label, isLastItem, icon }: Props) => ( + + + {label} + + {icon && } + + +); + +export default BreadcrumbTrigger; diff --git a/src/components/core/breadcrumb/index.ts b/src/components/core/breadcrumb/index.ts new file mode 100644 index 00000000..3fec5573 --- /dev/null +++ b/src/components/core/breadcrumb/index.ts @@ -0,0 +1,6 @@ +export { + default as Breadcrumb, + type BreadcrumbRecord, +} from "./Breadcrumb/Breadcrumb"; +export { default as BreadcrumbDropdown } from "./BreadcrumbDropdown/BreadcrumbDropdown"; +export { default as BreadcrumbTrigger } from "./BreadrcumbTrigger/BreadcrumbTrigger"; diff --git a/src/components/core/index.ts b/src/components/core/index.ts index ce09da16..1d66d445 100644 --- a/src/components/core/index.ts +++ b/src/components/core/index.ts @@ -1,8 +1,4 @@ -export { - default as Breadcrumb, - type BreadcrumbRecord, -} from "./Breadcrumb/Breadcrumb"; -export { default as BreadcrumbTrigger } from "./BreadrcumbTrigger/BreadcrumbTrigger"; +export * from "./breadcrumb"; export { default as CallToAction, type ActionButton, @@ -13,7 +9,6 @@ export { default as DestructiveAction, type Props as DestructiveActionProps, } from "./DestructiveAction/DestructiveAction"; -export { default as HoverCard } from "./HoverCard/HoverCard"; export { default as Image } from "./Image/Image"; export { default as Link } from "./Link/Link"; export { default as LogoLink } from "./LogoLink/LogoLink"; diff --git a/src/components/layout/AccountInformation/AccountInformation.tsx b/src/components/layout/AccountInformation/AccountInformation.tsx index 9bdc0fe2..fcb04dc1 100644 --- a/src/components/layout/AccountInformation/AccountInformation.tsx +++ b/src/components/layout/AccountInformation/AccountInformation.tsx @@ -36,7 +36,7 @@ const AccountInformation = () => { const router = useRouter(); const { user } = useAuth(); const isSmallViewport = useViewportSize({ - minWidth: token("breakpoints.md"), + minWidth: token("breakpoints.sm"), }); const userActions = useRef(null); diff --git a/src/lib/actions/getOrganizations.ts b/src/lib/actions/getOrganizations.ts index 16ecaaa6..a25bab6e 100644 --- a/src/lib/actions/getOrganizations.ts +++ b/src/lib/actions/getOrganizations.ts @@ -17,7 +17,7 @@ const getOrganizations = cache(async () => { const { organizations } = await sdk.Organizations({ userId: session.user?.rowId!, - isMember: false, + isMember: true, excludeRoles: [Role.Member], }); From 11a8b2637e6188f2a7f9ce63f306915e32206126 Mon Sep 17 00:00:00 2001 From: Beau Hawkinson <72956780+Twonarly1@users.noreply.github.com> Date: Wed, 7 May 2025 10:57:27 -0500 Subject: [PATCH 3/7] feat: implement a nested sub menu --- .../[projectSlug]/[feedbackId]/page.tsx | 21 +++++- .../core/breadcrumb/Breadcrumb/Breadcrumb.tsx | 11 ++- .../BreadcrumbDropdown/BreadcrumbDropdown.tsx | 70 +++++++++++++++++-- 3 files changed, 94 insertions(+), 8 deletions(-) diff --git a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx index 12aee1a3..ed666db2 100644 --- a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx +++ b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx @@ -14,6 +14,7 @@ import { useProjectStatusesQuery, useUpvoteQuery, } from "generated/graphql"; +import { getOrganization } from "lib/actions"; import { app } from "lib/config"; import { getSdk } from "lib/graphql"; import { getQueryClient } from "lib/util"; @@ -43,6 +44,8 @@ const FeedbackPage = async ({ params }: Props) => { if (!session) notFound(); + const organization = await getOrganization({ organizationSlug }); + const sdk = getSdk({ session }); const { post: feedback } = await sdk.FeedbackById({ rowId: feedbackId }); @@ -75,7 +78,23 @@ const FeedbackPage = async ({ params }: Props) => { }, { label: feedback?.project?.name ?? projectSlug, - href: `/organizations/${organizationSlug}/projects/${projectSlug}`, + // href: `/organizations/${organizationSlug}/projects/${projectSlug}`, + subItems: organization?.projects?.nodes?.length + ? organization?.projects?.nodes + .filter((p) => p?.slug !== projectSlug) + .map((project) => ({ + label: project!.name, + href: `/organizations/${organizationSlug}/projects/${project!.slug}`, + })) + : undefined, + nestedSubItems: organization?.projects?.nodes?.length + ? organization?.projects?.nodes + .filter((p) => p?.slug !== projectSlug) + .map((project) => ({ + label: project!.name, + href: `/organizations/${organizationSlug}/projects/${project!.slug}`, + })) + : undefined, }, { label: app.feedbackPage.breadcrumb, diff --git a/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx b/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx index cd601f3d..31a92449 100644 --- a/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx +++ b/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx @@ -13,6 +13,13 @@ const sharedIconStyles = { mx: 1.5, }; +interface NestedItemRecord { + /** Label for the sub-item. */ + label: string; + /** URL path the sub-item navigates to. */ + href: `/${string}`; +} + interface SubItemRecord { /** Label for the sub-item. */ label: string; @@ -27,6 +34,8 @@ export interface BreadcrumbRecord { href?: `/${string}`; /** Sub-items for the breadcrumb. */ subItems?: SubItemRecord[]; + /** Nested sub-items for the breadcrumb. */ + nestedSubItems?: NestedItemRecord[]; } interface Props { @@ -115,7 +124,7 @@ const Breadcrumb = ({ breadcrumbs }: Props) => { trigger={ diff --git a/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx b/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx index 53328dab..b0f251a1 100644 --- a/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx +++ b/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx @@ -1,4 +1,12 @@ -import { Menu, MenuItemGroup, MenuItem, Text } from "@omnidev/sigil"; +import { + Icon, + Menu, + MenuItemGroup, + MenuItem, + Text, + Flex, +} from "@omnidev/sigil"; +import { LuChevronRight } from "react-icons/lu"; import { Link } from "components/core"; @@ -15,11 +23,61 @@ interface Props extends MenuProps { */ const BreadcrumbDropdown = ({ breadcrumbs, trigger, ...rest }: Props) => ( - - {breadcrumbs.map(({ label, href }) => ( - - {href ? {label} : {label}} - + + {breadcrumbs.map(({ label, href, nestedSubItems }, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: + + {nestedSubItems?.length ? ( + + {label} + + + + } + > + + {nestedSubItems.map( + ({ label: nestedLabel, href: nestedHref }) => ( + + {nestedHref ? ( + + + {nestedLabel} + + + ) : ( + {nestedLabel} + )} + + ), + )} + + + ) : ( + + {href ? ( + + + {label} + + + ) : ( + {label} + )} + + )} + ))} From 078dceaf90ce231102011febaa5099cd8c68c213 Mon Sep 17 00:00:00 2001 From: Beau Hawkinson <72956780+Twonarly1@users.noreply.github.com> Date: Thu, 15 May 2025 13:59:05 -0500 Subject: [PATCH 4/7] chore: merge master --- src/generated/graphql.sdk.ts | 70 ++++++++++++++++++++++++++++++++++++ src/generated/graphql.ts | 70 ++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) diff --git a/src/generated/graphql.sdk.ts b/src/generated/graphql.sdk.ts index 81f544a7..c169319e 100644 --- a/src/generated/graphql.sdk.ts +++ b/src/generated/graphql.sdk.ts @@ -76,8 +76,13 @@ export type BooleanFilter = { export type Comment = { __typename?: 'Comment'; + /** Reads and enables pagination through a set of `Comment`. */ + childComments: CommentConnection; createdAt?: Maybe; message?: Maybe; + /** Reads a single `Comment` that is related to this `Comment`. */ + parent?: Maybe; + parentId?: Maybe; /** Reads a single `Post` that is related to this `Comment`. */ post?: Maybe; postId: Scalars['UUID']['output']; @@ -88,6 +93,18 @@ export type Comment = { userId: Scalars['UUID']['output']; }; + +export type CommentChildCommentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + condition?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; +}; + export type CommentAggregates = { __typename?: 'CommentAggregates'; /** Distinct count aggregates across the matching connection (ignoring before/after/first/last/offset) */ @@ -109,6 +126,8 @@ export type CommentCondition = { createdAt?: InputMaybe; /** Checks for equality with the object’s `message` field. */ message?: InputMaybe; + /** Checks for equality with the object’s `parentId` field. */ + parentId?: InputMaybe; /** Checks for equality with the object’s `postId` field. */ postId?: InputMaybe; /** Checks for equality with the object’s `rowId` field. */ @@ -146,6 +165,7 @@ export type CommentConnectionGroupedAggregatesArgs = { export type CommentDistinctCountAggregateFilter = { createdAt?: InputMaybe; message?: InputMaybe; + parentId?: InputMaybe; postId?: InputMaybe; rowId?: InputMaybe; updatedAt?: InputMaybe; @@ -158,6 +178,8 @@ export type CommentDistinctCountAggregates = { createdAt?: Maybe; /** Distinct count of message across the matching connection */ message?: Maybe; + /** Distinct count of parentId across the matching connection */ + parentId?: Maybe; /** Distinct count of postId across the matching connection */ postId?: Maybe; /** Distinct count of rowId across the matching connection */ @@ -181,6 +203,10 @@ export type CommentEdge = { export type CommentFilter = { /** Checks for all expressions in this list. */ and?: InputMaybe>; + /** Filter by the object’s `childComments` relation. */ + childComments?: InputMaybe; + /** Some related `childComments` exist. */ + childCommentsExist?: InputMaybe; /** Filter by the object’s `createdAt` field. */ createdAt?: InputMaybe; /** Filter by the object’s `message` field. */ @@ -189,6 +215,12 @@ export type CommentFilter = { not?: InputMaybe; /** Checks for any expressions in this list. */ or?: InputMaybe>; + /** Filter by the object’s `parent` relation. */ + parent?: InputMaybe; + /** A related `parent` exists. */ + parentExists?: InputMaybe; + /** Filter by the object’s `parentId` field. */ + parentId?: InputMaybe; /** Filter by the object’s `post` relation. */ post?: InputMaybe; /** Filter by the object’s `postId` field. */ @@ -209,6 +241,7 @@ export enum CommentGroupBy { CreatedAtTruncatedToDay = 'CREATED_AT_TRUNCATED_TO_DAY', CreatedAtTruncatedToHour = 'CREATED_AT_TRUNCATED_TO_HOUR', Message = 'MESSAGE', + ParentId = 'PARENT_ID', PostId = 'POST_ID', UpdatedAt = 'UPDATED_AT', UpdatedAtTruncatedToDay = 'UPDATED_AT_TRUNCATED_TO_DAY', @@ -280,6 +313,7 @@ export type CommentHavingVarianceSampleInput = { export type CommentInput = { createdAt?: InputMaybe; message?: InputMaybe; + parentId?: InputMaybe; postId: Scalars['UUID']['input']; rowId?: InputMaybe; updatedAt?: InputMaybe; @@ -288,11 +322,29 @@ export type CommentInput = { /** Methods to use when ordering `Comment`. */ export enum CommentOrderBy { + ChildCommentsCountAsc = 'CHILD_COMMENTS_COUNT_ASC', + ChildCommentsCountDesc = 'CHILD_COMMENTS_COUNT_DESC', + ChildCommentsDistinctCountCreatedAtAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_CREATED_AT_ASC', + ChildCommentsDistinctCountCreatedAtDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_CREATED_AT_DESC', + ChildCommentsDistinctCountMessageAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_MESSAGE_ASC', + ChildCommentsDistinctCountMessageDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_MESSAGE_DESC', + ChildCommentsDistinctCountParentIdAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_PARENT_ID_ASC', + ChildCommentsDistinctCountParentIdDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_PARENT_ID_DESC', + ChildCommentsDistinctCountPostIdAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_POST_ID_ASC', + ChildCommentsDistinctCountPostIdDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_POST_ID_DESC', + ChildCommentsDistinctCountRowIdAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_ROW_ID_ASC', + ChildCommentsDistinctCountRowIdDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_ROW_ID_DESC', + ChildCommentsDistinctCountUpdatedAtAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_UPDATED_AT_ASC', + ChildCommentsDistinctCountUpdatedAtDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_UPDATED_AT_DESC', + ChildCommentsDistinctCountUserIdAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_USER_ID_ASC', + ChildCommentsDistinctCountUserIdDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_USER_ID_DESC', CreatedAtAsc = 'CREATED_AT_ASC', CreatedAtDesc = 'CREATED_AT_DESC', MessageAsc = 'MESSAGE_ASC', MessageDesc = 'MESSAGE_DESC', Natural = 'NATURAL', + ParentIdAsc = 'PARENT_ID_ASC', + ParentIdDesc = 'PARENT_ID_DESC', PostIdAsc = 'POST_ID_ASC', PostIdDesc = 'POST_ID_DESC', PrimaryKeyAsc = 'PRIMARY_KEY_ASC', @@ -309,12 +361,25 @@ export enum CommentOrderBy { export type CommentPatch = { createdAt?: InputMaybe; message?: InputMaybe; + parentId?: InputMaybe; postId?: InputMaybe; rowId?: InputMaybe; updatedAt?: InputMaybe; userId?: InputMaybe; }; +/** A filter to be used against many `Comment` object types. All fields are combined with a logical ‘and.’ */ +export type CommentToManyCommentFilter = { + /** Aggregates across related `Comment` match the filter criteria. */ + aggregates?: InputMaybe; + /** Every related `Comment` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + every?: InputMaybe; + /** No related `Comment` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + none?: InputMaybe; + /** Some related `Comment` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + some?: InputMaybe; +}; + /** All input for the create `Comment` mutation. */ export type CreateCommentInput = { /** @@ -2597,6 +2662,8 @@ export enum PostOrderBy { CommentsDistinctCountCreatedAtDesc = 'COMMENTS_DISTINCT_COUNT_CREATED_AT_DESC', CommentsDistinctCountMessageAsc = 'COMMENTS_DISTINCT_COUNT_MESSAGE_ASC', CommentsDistinctCountMessageDesc = 'COMMENTS_DISTINCT_COUNT_MESSAGE_DESC', + CommentsDistinctCountParentIdAsc = 'COMMENTS_DISTINCT_COUNT_PARENT_ID_ASC', + CommentsDistinctCountParentIdDesc = 'COMMENTS_DISTINCT_COUNT_PARENT_ID_DESC', CommentsDistinctCountPostIdAsc = 'COMMENTS_DISTINCT_COUNT_POST_ID_ASC', CommentsDistinctCountPostIdDesc = 'COMMENTS_DISTINCT_COUNT_POST_ID_DESC', CommentsDistinctCountRowIdAsc = 'COMMENTS_DISTINCT_COUNT_ROW_ID_ASC', @@ -3826,6 +3893,7 @@ export type StringFilter = { export enum Tier { Basic = 'basic', Enterprise = 'enterprise', + Free = 'free', Team = 'team' } @@ -4757,6 +4825,8 @@ export enum UserOrderBy { CommentsDistinctCountCreatedAtDesc = 'COMMENTS_DISTINCT_COUNT_CREATED_AT_DESC', CommentsDistinctCountMessageAsc = 'COMMENTS_DISTINCT_COUNT_MESSAGE_ASC', CommentsDistinctCountMessageDesc = 'COMMENTS_DISTINCT_COUNT_MESSAGE_DESC', + CommentsDistinctCountParentIdAsc = 'COMMENTS_DISTINCT_COUNT_PARENT_ID_ASC', + CommentsDistinctCountParentIdDesc = 'COMMENTS_DISTINCT_COUNT_PARENT_ID_DESC', CommentsDistinctCountPostIdAsc = 'COMMENTS_DISTINCT_COUNT_POST_ID_ASC', CommentsDistinctCountPostIdDesc = 'COMMENTS_DISTINCT_COUNT_POST_ID_DESC', CommentsDistinctCountRowIdAsc = 'COMMENTS_DISTINCT_COUNT_ROW_ID_ASC', diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index b7024485..4c0899d4 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -75,8 +75,13 @@ export type BooleanFilter = { export type Comment = { __typename?: 'Comment'; + /** Reads and enables pagination through a set of `Comment`. */ + childComments: CommentConnection; createdAt?: Maybe; message?: Maybe; + /** Reads a single `Comment` that is related to this `Comment`. */ + parent?: Maybe; + parentId?: Maybe; /** Reads a single `Post` that is related to this `Comment`. */ post?: Maybe; postId: Scalars['UUID']['output']; @@ -87,6 +92,18 @@ export type Comment = { userId: Scalars['UUID']['output']; }; + +export type CommentChildCommentsArgs = { + after?: InputMaybe; + before?: InputMaybe; + condition?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; +}; + export type CommentAggregates = { __typename?: 'CommentAggregates'; /** Distinct count aggregates across the matching connection (ignoring before/after/first/last/offset) */ @@ -108,6 +125,8 @@ export type CommentCondition = { createdAt?: InputMaybe; /** Checks for equality with the object’s `message` field. */ message?: InputMaybe; + /** Checks for equality with the object’s `parentId` field. */ + parentId?: InputMaybe; /** Checks for equality with the object’s `postId` field. */ postId?: InputMaybe; /** Checks for equality with the object’s `rowId` field. */ @@ -145,6 +164,7 @@ export type CommentConnectionGroupedAggregatesArgs = { export type CommentDistinctCountAggregateFilter = { createdAt?: InputMaybe; message?: InputMaybe; + parentId?: InputMaybe; postId?: InputMaybe; rowId?: InputMaybe; updatedAt?: InputMaybe; @@ -157,6 +177,8 @@ export type CommentDistinctCountAggregates = { createdAt?: Maybe; /** Distinct count of message across the matching connection */ message?: Maybe; + /** Distinct count of parentId across the matching connection */ + parentId?: Maybe; /** Distinct count of postId across the matching connection */ postId?: Maybe; /** Distinct count of rowId across the matching connection */ @@ -180,6 +202,10 @@ export type CommentEdge = { export type CommentFilter = { /** Checks for all expressions in this list. */ and?: InputMaybe>; + /** Filter by the object’s `childComments` relation. */ + childComments?: InputMaybe; + /** Some related `childComments` exist. */ + childCommentsExist?: InputMaybe; /** Filter by the object’s `createdAt` field. */ createdAt?: InputMaybe; /** Filter by the object’s `message` field. */ @@ -188,6 +214,12 @@ export type CommentFilter = { not?: InputMaybe; /** Checks for any expressions in this list. */ or?: InputMaybe>; + /** Filter by the object’s `parent` relation. */ + parent?: InputMaybe; + /** A related `parent` exists. */ + parentExists?: InputMaybe; + /** Filter by the object’s `parentId` field. */ + parentId?: InputMaybe; /** Filter by the object’s `post` relation. */ post?: InputMaybe; /** Filter by the object’s `postId` field. */ @@ -208,6 +240,7 @@ export enum CommentGroupBy { CreatedAtTruncatedToDay = 'CREATED_AT_TRUNCATED_TO_DAY', CreatedAtTruncatedToHour = 'CREATED_AT_TRUNCATED_TO_HOUR', Message = 'MESSAGE', + ParentId = 'PARENT_ID', PostId = 'POST_ID', UpdatedAt = 'UPDATED_AT', UpdatedAtTruncatedToDay = 'UPDATED_AT_TRUNCATED_TO_DAY', @@ -279,6 +312,7 @@ export type CommentHavingVarianceSampleInput = { export type CommentInput = { createdAt?: InputMaybe; message?: InputMaybe; + parentId?: InputMaybe; postId: Scalars['UUID']['input']; rowId?: InputMaybe; updatedAt?: InputMaybe; @@ -287,11 +321,29 @@ export type CommentInput = { /** Methods to use when ordering `Comment`. */ export enum CommentOrderBy { + ChildCommentsCountAsc = 'CHILD_COMMENTS_COUNT_ASC', + ChildCommentsCountDesc = 'CHILD_COMMENTS_COUNT_DESC', + ChildCommentsDistinctCountCreatedAtAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_CREATED_AT_ASC', + ChildCommentsDistinctCountCreatedAtDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_CREATED_AT_DESC', + ChildCommentsDistinctCountMessageAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_MESSAGE_ASC', + ChildCommentsDistinctCountMessageDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_MESSAGE_DESC', + ChildCommentsDistinctCountParentIdAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_PARENT_ID_ASC', + ChildCommentsDistinctCountParentIdDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_PARENT_ID_DESC', + ChildCommentsDistinctCountPostIdAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_POST_ID_ASC', + ChildCommentsDistinctCountPostIdDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_POST_ID_DESC', + ChildCommentsDistinctCountRowIdAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_ROW_ID_ASC', + ChildCommentsDistinctCountRowIdDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_ROW_ID_DESC', + ChildCommentsDistinctCountUpdatedAtAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_UPDATED_AT_ASC', + ChildCommentsDistinctCountUpdatedAtDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_UPDATED_AT_DESC', + ChildCommentsDistinctCountUserIdAsc = 'CHILD_COMMENTS_DISTINCT_COUNT_USER_ID_ASC', + ChildCommentsDistinctCountUserIdDesc = 'CHILD_COMMENTS_DISTINCT_COUNT_USER_ID_DESC', CreatedAtAsc = 'CREATED_AT_ASC', CreatedAtDesc = 'CREATED_AT_DESC', MessageAsc = 'MESSAGE_ASC', MessageDesc = 'MESSAGE_DESC', Natural = 'NATURAL', + ParentIdAsc = 'PARENT_ID_ASC', + ParentIdDesc = 'PARENT_ID_DESC', PostIdAsc = 'POST_ID_ASC', PostIdDesc = 'POST_ID_DESC', PrimaryKeyAsc = 'PRIMARY_KEY_ASC', @@ -308,12 +360,25 @@ export enum CommentOrderBy { export type CommentPatch = { createdAt?: InputMaybe; message?: InputMaybe; + parentId?: InputMaybe; postId?: InputMaybe; rowId?: InputMaybe; updatedAt?: InputMaybe; userId?: InputMaybe; }; +/** A filter to be used against many `Comment` object types. All fields are combined with a logical ‘and.’ */ +export type CommentToManyCommentFilter = { + /** Aggregates across related `Comment` match the filter criteria. */ + aggregates?: InputMaybe; + /** Every related `Comment` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + every?: InputMaybe; + /** No related `Comment` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + none?: InputMaybe; + /** Some related `Comment` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + some?: InputMaybe; +}; + /** All input for the create `Comment` mutation. */ export type CreateCommentInput = { /** @@ -2596,6 +2661,8 @@ export enum PostOrderBy { CommentsDistinctCountCreatedAtDesc = 'COMMENTS_DISTINCT_COUNT_CREATED_AT_DESC', CommentsDistinctCountMessageAsc = 'COMMENTS_DISTINCT_COUNT_MESSAGE_ASC', CommentsDistinctCountMessageDesc = 'COMMENTS_DISTINCT_COUNT_MESSAGE_DESC', + CommentsDistinctCountParentIdAsc = 'COMMENTS_DISTINCT_COUNT_PARENT_ID_ASC', + CommentsDistinctCountParentIdDesc = 'COMMENTS_DISTINCT_COUNT_PARENT_ID_DESC', CommentsDistinctCountPostIdAsc = 'COMMENTS_DISTINCT_COUNT_POST_ID_ASC', CommentsDistinctCountPostIdDesc = 'COMMENTS_DISTINCT_COUNT_POST_ID_DESC', CommentsDistinctCountRowIdAsc = 'COMMENTS_DISTINCT_COUNT_ROW_ID_ASC', @@ -3825,6 +3892,7 @@ export type StringFilter = { export enum Tier { Basic = 'basic', Enterprise = 'enterprise', + Free = 'free', Team = 'team' } @@ -4756,6 +4824,8 @@ export enum UserOrderBy { CommentsDistinctCountCreatedAtDesc = 'COMMENTS_DISTINCT_COUNT_CREATED_AT_DESC', CommentsDistinctCountMessageAsc = 'COMMENTS_DISTINCT_COUNT_MESSAGE_ASC', CommentsDistinctCountMessageDesc = 'COMMENTS_DISTINCT_COUNT_MESSAGE_DESC', + CommentsDistinctCountParentIdAsc = 'COMMENTS_DISTINCT_COUNT_PARENT_ID_ASC', + CommentsDistinctCountParentIdDesc = 'COMMENTS_DISTINCT_COUNT_PARENT_ID_DESC', CommentsDistinctCountPostIdAsc = 'COMMENTS_DISTINCT_COUNT_POST_ID_ASC', CommentsDistinctCountPostIdDesc = 'COMMENTS_DISTINCT_COUNT_POST_ID_DESC', CommentsDistinctCountRowIdAsc = 'COMMENTS_DISTINCT_COUNT_ROW_ID_ASC', From 131b8eb728fdd19f6aba90752b4f429d1a23210d Mon Sep 17 00:00:00 2001 From: Beau Hawkinson <72956780+Twonarly1@users.noreply.github.com> Date: Mon, 19 May 2025 12:06:35 -0500 Subject: [PATCH 5/7] refactor: breadcrumb dropdwon to use router and select funtion for menu items --- .../[projectSlug]/[feedbackId]/page.tsx | 25 ++-- .../core/breadcrumb/Breadcrumb/Breadcrumb.tsx | 4 +- .../BreadcrumbDropdown/BreadcrumbDropdown.tsx | 125 +++++++++--------- 3 files changed, 79 insertions(+), 75 deletions(-) diff --git a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx index 88c440e0..853226c5 100644 --- a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx +++ b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx @@ -45,6 +45,8 @@ const FeedbackPage = async ({ params }: Props) => { getFeedback({ feedbackId }), ]); + const numberOfProjects = organization?.projects?.nodes?.length ?? 0; + if (!feedback) notFound(); const queryClient = getQueryClient(); @@ -64,23 +66,24 @@ const FeedbackPage = async ({ params }: Props) => { }, { label: feedback?.project?.name ?? projectSlug, - // href: `/organizations/${organizationSlug}/projects/${projectSlug}`, - subItems: organization?.projects?.nodes?.length - ? organization?.projects?.nodes - .filter((p) => p?.slug !== projectSlug) - .map((project) => ({ + href: + numberOfProjects <= 2 + ? `/organizations/${organizationSlug}/projects/${projectSlug}` + : undefined, + subItems: + numberOfProjects > 1 + ? organization?.projects?.nodes.map((project) => ({ label: project!.name, href: `/organizations/${organizationSlug}/projects/${project!.slug}`, })) - : undefined, - nestedSubItems: organization?.projects?.nodes?.length - ? organization?.projects?.nodes - .filter((p) => p?.slug !== projectSlug) - .map((project) => ({ + : undefined, + nestedSubItems: + numberOfProjects > 1 + ? organization?.projects?.nodes.map((project) => ({ label: project!.name, href: `/organizations/${organizationSlug}/projects/${project!.slug}`, })) - : undefined, + : undefined, }, { label: app.feedbackPage.breadcrumb, diff --git a/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx b/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx index 31a92449..6828c0fa 100644 --- a/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx +++ b/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx @@ -60,7 +60,7 @@ const Breadcrumb = ({ breadcrumbs }: Props) => { - {/* Large breakpoint and above hidden */} + {/* base viewport */} @@ -109,7 +109,7 @@ const Breadcrumb = ({ breadcrumbs }: Props) => { )} - {/* Mobile hidden */} + {/* Large viewport */} {breadcrumbs.map(({ label, href, subItems }, index) => { const isLastItem = breadcrumbs.length - 1 === index; diff --git a/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx b/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx index b0f251a1..74e4dffe 100644 --- a/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx +++ b/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx @@ -8,10 +8,9 @@ import { } from "@omnidev/sigil"; import { LuChevronRight } from "react-icons/lu"; -import { Link } from "components/core"; - import type { BreadcrumbRecord } from "components/core/breadcrumb"; import type { MenuProps } from "@omnidev/sigil"; +import { useRouter } from "next/navigation"; interface Props extends MenuProps { /** Array of navigation breadcrumbs. */ @@ -21,66 +20,68 @@ interface Props extends MenuProps { /** * Breadcrumb dropdown. */ -const BreadcrumbDropdown = ({ breadcrumbs, trigger, ...rest }: Props) => ( - - - {breadcrumbs.map(({ label, href, nestedSubItems }, index) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: - - {nestedSubItems?.length ? ( - - {label} +const BreadcrumbDropdown = ({ breadcrumbs, trigger, ...rest }: Props) => { + const router = useRouter(); + + return ( + + + {breadcrumbs.map(({ label, href, nestedSubItems }, index) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: + + {nestedSubItems?.length ? ( + + {label} - - - } - > - - {nestedSubItems.map( - ({ label: nestedLabel, href: nestedHref }) => ( - - {nestedHref ? ( - - - {nestedLabel} - - - ) : ( - {nestedLabel} - )} - - ), - )} - - - ) : ( - - {href ? ( - - - {label} - - - ) : ( - {label} - )} - - )} - - ))} - - -); + + + } + > + + {nestedSubItems.map( + ({ label: nestedLabel, href: nestedHref }) => ( + nestedHref && router.push(nestedHref)} + > + + {nestedLabel} + + + ), + )} + + + ) : ( + href && router.push(href)} + > + + {label} + + + )} + + ))} + + + ); +}; export default BreadcrumbDropdown; From ff82d5e45cefba020a1122dfb19195897c67ef29 Mon Sep 17 00:00:00 2001 From: Beau Hawkinson <72956780+Twonarly1@users.noreply.github.com> Date: Wed, 21 May 2025 07:37:15 -0500 Subject: [PATCH 6/7] chore: remove commented code and unused files --- .../organizations/[organizationSlug]/page.tsx | 10 +- src/generated/graphql.sdk.ts | 434 +++++++++++++++++- src/generated/graphql.ts | 434 +++++++++++++++++- src/lib/actions/getOrganizations.ts | 29 -- src/lib/actions/index.ts | 1 - 5 files changed, 865 insertions(+), 43 deletions(-) delete mode 100644 src/lib/actions/getOrganizations.ts diff --git a/src/app/organizations/[organizationSlug]/page.tsx b/src/app/organizations/[organizationSlug]/page.tsx index 2912f6b7..c896ad5e 100644 --- a/src/app/organizations/[organizationSlug]/page.tsx +++ b/src/app/organizations/[organizationSlug]/page.tsx @@ -18,7 +18,7 @@ import { useOrganizationRoleQuery, } from "generated/graphql"; import { Grid } from "generated/panda/jsx"; -import { getOrganization, getOrganizations, getOwnerTier } from "lib/actions"; +import { getOrganization, getOwnerTier } from "lib/actions"; import { app } from "lib/config"; import { MAX_NUMBER_OF_PROJECTS } from "lib/constants"; import { getSdk } from "lib/graphql"; @@ -56,11 +56,9 @@ const OrganizationPage = async ({ params }: Props) => { const [ organization, - organizations, { isOwnerSubscribed, hasBasicTierPrivileges, hasTeamTierPrivileges }, ] = await Promise.all([ getOrganization({ organizationSlug }), - getOrganizations(), getOwnerTier({ organizationSlug }), ]); @@ -93,12 +91,6 @@ const OrganizationPage = async ({ params }: Props) => { }, { label: organization.name ?? organizationSlug, - // subItems: organizations?.length - // ? organizations.map((organization) => ({ - // label: organization?.name!, - // href: `/organizations/${organization!.slug}`, - // })) - // : undefined, }, ]; diff --git a/src/generated/graphql.sdk.ts b/src/generated/graphql.sdk.ts index b6f7b56b..4c030600 100644 --- a/src/generated/graphql.sdk.ts +++ b/src/generated/graphql.sdk.ts @@ -644,6 +644,39 @@ export type CreateProjectPayloadProjectEdgeArgs = { orderBy?: Array; }; +/** All input for the create `ProjectSocial` mutation. */ +export type CreateProjectSocialInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: InputMaybe; + /** The `ProjectSocial` to be created by this mutation. */ + projectSocial: ProjectSocialInput; +}; + +/** The output of our create `ProjectSocial` mutation. */ +export type CreateProjectSocialPayload = { + __typename?: 'CreateProjectSocialPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `ProjectSocial` that was created by this mutation. */ + projectSocial?: Maybe; + /** An edge for our `ProjectSocial`. May be used by Relay 1. */ + projectSocialEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our create `ProjectSocial` mutation. */ +export type CreateProjectSocialPayloadProjectSocialEdgeArgs = { + orderBy?: Array; +}; + /** All input for the create `Upvote` mutation. */ export type CreateUpvoteInput = { /** @@ -992,6 +1025,38 @@ export type DeleteProjectPayloadProjectEdgeArgs = { orderBy?: Array; }; +/** All input for the `deleteProjectSocial` mutation. */ +export type DeleteProjectSocialInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: InputMaybe; + rowId: Scalars['UUID']['input']; +}; + +/** The output of our delete `ProjectSocial` mutation. */ +export type DeleteProjectSocialPayload = { + __typename?: 'DeleteProjectSocialPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `ProjectSocial` that was deleted by this mutation. */ + projectSocial?: Maybe; + /** An edge for our `ProjectSocial`. May be used by Relay 1. */ + projectSocialEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our delete `ProjectSocial` mutation. */ +export type DeleteProjectSocialPayloadProjectSocialEdgeArgs = { + orderBy?: Array; +}; + /** All input for the `deleteUpvote` mutation. */ export type DeleteUpvoteInput = { /** @@ -1759,6 +1824,8 @@ export type Mutation = { createPostStatus?: Maybe; /** Creates a single `Project`. */ createProject?: Maybe; + /** Creates a single `ProjectSocial`. */ + createProjectSocial?: Maybe; /** Creates a single `Upvote`. */ createUpvote?: Maybe; /** Creates a single `User`. */ @@ -1779,6 +1846,8 @@ export type Mutation = { deletePostStatus?: Maybe; /** Deletes a single `Project` using a unique key. */ deleteProject?: Maybe; + /** Deletes a single `ProjectSocial` using a unique key. */ + deleteProjectSocial?: Maybe; /** Deletes a single `Upvote` using a unique key. */ deleteUpvote?: Maybe; /** Deletes a single `User` using a unique key. */ @@ -1799,6 +1868,8 @@ export type Mutation = { updatePostStatus?: Maybe; /** Updates a single `Project` using a unique key and a patch. */ updateProject?: Maybe; + /** Updates a single `ProjectSocial` using a unique key and a patch. */ + updateProjectSocial?: Maybe; /** Updates a single `Upvote` using a unique key and a patch. */ updateUpvote?: Maybe; /** Updates a single `User` using a unique key and a patch. */ @@ -1854,6 +1925,12 @@ export type MutationCreateProjectArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationCreateProjectSocialArgs = { + input: CreateProjectSocialInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationCreateUpvoteArgs = { input: CreateUpvoteInput; @@ -1914,6 +1991,12 @@ export type MutationDeleteProjectArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationDeleteProjectSocialArgs = { + input: DeleteProjectSocialInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationDeleteUpvoteArgs = { input: DeleteUpvoteInput; @@ -1974,6 +2057,12 @@ export type MutationUpdateProjectArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationUpdateProjectSocialArgs = { + input: UpdateProjectSocialInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationUpdateUpvoteArgs = { input: UpdateUpvoteInput; @@ -2275,6 +2364,8 @@ export enum OrganizationOrderBy { ProjectsDistinctCountSlugDesc = 'PROJECTS_DISTINCT_COUNT_SLUG_DESC', ProjectsDistinctCountUpdatedAtAsc = 'PROJECTS_DISTINCT_COUNT_UPDATED_AT_ASC', ProjectsDistinctCountUpdatedAtDesc = 'PROJECTS_DISTINCT_COUNT_UPDATED_AT_DESC', + ProjectsDistinctCountWebsiteAsc = 'PROJECTS_DISTINCT_COUNT_WEBSITE_ASC', + ProjectsDistinctCountWebsiteDesc = 'PROJECTS_DISTINCT_COUNT_WEBSITE_DESC', RowIdAsc = 'ROW_ID_ASC', RowIdDesc = 'ROW_ID_DESC', SlugAsc = 'SLUG_ASC', @@ -3097,9 +3188,12 @@ export type Project = { postStatuses: PostStatusConnection; /** Reads and enables pagination through a set of `Post`. */ posts: PostConnection; + /** Reads and enables pagination through a set of `ProjectSocial`. */ + projectSocials: ProjectSocialConnection; rowId: Scalars['UUID']['output']; slug: Scalars['String']['output']; updatedAt?: Maybe; + website?: Maybe; }; @@ -3126,6 +3220,18 @@ export type ProjectPostsArgs = { orderBy?: InputMaybe>; }; + +export type ProjectProjectSocialsArgs = { + after?: InputMaybe; + before?: InputMaybe; + condition?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; +}; + export type ProjectAggregates = { __typename?: 'ProjectAggregates'; /** Distinct count aggregates across the matching connection (ignoring before/after/first/last/offset) */ @@ -3159,6 +3265,8 @@ export type ProjectCondition = { slug?: InputMaybe; /** Checks for equality with the object’s `updatedAt` field. */ updatedAt?: InputMaybe; + /** Checks for equality with the object’s `website` field. */ + website?: InputMaybe; }; /** A connection to a list of `Project` values. */ @@ -3194,6 +3302,7 @@ export type ProjectDistinctCountAggregateFilter = { rowId?: InputMaybe; slug?: InputMaybe; updatedAt?: InputMaybe; + website?: InputMaybe; }; export type ProjectDistinctCountAggregates = { @@ -3214,6 +3323,8 @@ export type ProjectDistinctCountAggregates = { slug?: Maybe; /** Distinct count of updatedAt across the matching connection */ updatedAt?: Maybe; + /** Distinct count of website across the matching connection */ + website?: Maybe; }; /** A `Project` edge in the connection. */ @@ -3253,12 +3364,18 @@ export type ProjectFilter = { posts?: InputMaybe; /** Some related `posts` exist. */ postsExist?: InputMaybe; + /** Filter by the object’s `projectSocials` relation. */ + projectSocials?: InputMaybe; + /** Some related `projectSocials` exist. */ + projectSocialsExist?: InputMaybe; /** Filter by the object’s `rowId` field. */ rowId?: InputMaybe; /** Filter by the object’s `slug` field. */ slug?: InputMaybe; /** Filter by the object’s `updatedAt` field. */ updatedAt?: InputMaybe; + /** Filter by the object’s `website` field. */ + website?: InputMaybe; }; /** Grouping methods for `Project` for usage during aggregation. */ @@ -3273,7 +3390,8 @@ export enum ProjectGroupBy { Slug = 'SLUG', UpdatedAt = 'UPDATED_AT', UpdatedAtTruncatedToDay = 'UPDATED_AT_TRUNCATED_TO_DAY', - UpdatedAtTruncatedToHour = 'UPDATED_AT_TRUNCATED_TO_HOUR' + UpdatedAtTruncatedToHour = 'UPDATED_AT_TRUNCATED_TO_HOUR', + Website = 'WEBSITE' } export type ProjectHavingAverageInput = { @@ -3346,6 +3464,7 @@ export type ProjectInput = { rowId?: InputMaybe; slug: Scalars['String']['input']; updatedAt?: InputMaybe; + website?: InputMaybe; }; /** Methods to use when ordering `Project`. */ @@ -3401,12 +3520,26 @@ export enum ProjectOrderBy { PostStatusesDistinctCountUpdatedAtDesc = 'POST_STATUSES_DISTINCT_COUNT_UPDATED_AT_DESC', PrimaryKeyAsc = 'PRIMARY_KEY_ASC', PrimaryKeyDesc = 'PRIMARY_KEY_DESC', + ProjectSocialsCountAsc = 'PROJECT_SOCIALS_COUNT_ASC', + ProjectSocialsCountDesc = 'PROJECT_SOCIALS_COUNT_DESC', + ProjectSocialsDistinctCountCreatedAtAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_CREATED_AT_ASC', + ProjectSocialsDistinctCountCreatedAtDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_CREATED_AT_DESC', + ProjectSocialsDistinctCountProjectIdAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_PROJECT_ID_ASC', + ProjectSocialsDistinctCountProjectIdDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_PROJECT_ID_DESC', + ProjectSocialsDistinctCountRowIdAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_ROW_ID_ASC', + ProjectSocialsDistinctCountRowIdDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_ROW_ID_DESC', + ProjectSocialsDistinctCountUpdatedAtAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_UPDATED_AT_ASC', + ProjectSocialsDistinctCountUpdatedAtDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_UPDATED_AT_DESC', + ProjectSocialsDistinctCountUrlAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_URL_ASC', + ProjectSocialsDistinctCountUrlDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_URL_DESC', RowIdAsc = 'ROW_ID_ASC', RowIdDesc = 'ROW_ID_DESC', SlugAsc = 'SLUG_ASC', SlugDesc = 'SLUG_DESC', UpdatedAtAsc = 'UPDATED_AT_ASC', - UpdatedAtDesc = 'UPDATED_AT_DESC' + UpdatedAtDesc = 'UPDATED_AT_DESC', + WebsiteAsc = 'WEBSITE_ASC', + WebsiteDesc = 'WEBSITE_DESC' } /** Represents an update to a `Project`. Fields that are set will be updated. */ @@ -3419,6 +3552,234 @@ export type ProjectPatch = { rowId?: InputMaybe; slug?: InputMaybe; updatedAt?: InputMaybe; + website?: InputMaybe; +}; + +export type ProjectSocial = { + __typename?: 'ProjectSocial'; + createdAt?: Maybe; + /** Reads a single `Project` that is related to this `ProjectSocial`. */ + project?: Maybe; + projectId: Scalars['UUID']['output']; + rowId: Scalars['UUID']['output']; + updatedAt?: Maybe; + url: Scalars['String']['output']; +}; + +export type ProjectSocialAggregates = { + __typename?: 'ProjectSocialAggregates'; + /** Distinct count aggregates across the matching connection (ignoring before/after/first/last/offset) */ + distinctCount?: Maybe; + keys?: Maybe>>; +}; + +/** A filter to be used against aggregates of `ProjectSocial` object types. */ +export type ProjectSocialAggregatesFilter = { + /** Distinct count aggregate over matching `ProjectSocial` objects. */ + distinctCount?: InputMaybe; + /** A filter that must pass for the relevant `ProjectSocial` object to be included within the aggregate. */ + filter?: InputMaybe; +}; + +/** + * A condition to be used against `ProjectSocial` object types. All fields are + * tested for equality and combined with a logical ‘and.’ + */ +export type ProjectSocialCondition = { + /** Checks for equality with the object’s `createdAt` field. */ + createdAt?: InputMaybe; + /** Checks for equality with the object’s `projectId` field. */ + projectId?: InputMaybe; + /** Checks for equality with the object’s `rowId` field. */ + rowId?: InputMaybe; + /** Checks for equality with the object’s `updatedAt` field. */ + updatedAt?: InputMaybe; + /** Checks for equality with the object’s `url` field. */ + url?: InputMaybe; +}; + +/** A connection to a list of `ProjectSocial` values. */ +export type ProjectSocialConnection = { + __typename?: 'ProjectSocialConnection'; + /** Aggregates across the matching connection (ignoring before/after/first/last/offset) */ + aggregates?: Maybe; + /** A list of edges which contains the `ProjectSocial` and cursor to aid in pagination. */ + edges: Array>; + /** Grouped aggregates across the matching connection (ignoring before/after/first/last/offset) */ + groupedAggregates?: Maybe>; + /** A list of `ProjectSocial` objects. */ + nodes: Array>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** The count of *all* `ProjectSocial` you could get from the connection. */ + totalCount: Scalars['Int']['output']; +}; + + +/** A connection to a list of `ProjectSocial` values. */ +export type ProjectSocialConnectionGroupedAggregatesArgs = { + groupBy: Array; + having?: InputMaybe; +}; + +export type ProjectSocialDistinctCountAggregateFilter = { + createdAt?: InputMaybe; + projectId?: InputMaybe; + rowId?: InputMaybe; + updatedAt?: InputMaybe; + url?: InputMaybe; +}; + +export type ProjectSocialDistinctCountAggregates = { + __typename?: 'ProjectSocialDistinctCountAggregates'; + /** Distinct count of createdAt across the matching connection */ + createdAt?: Maybe; + /** Distinct count of projectId across the matching connection */ + projectId?: Maybe; + /** Distinct count of rowId across the matching connection */ + rowId?: Maybe; + /** Distinct count of updatedAt across the matching connection */ + updatedAt?: Maybe; + /** Distinct count of url across the matching connection */ + url?: Maybe; +}; + +/** A `ProjectSocial` edge in the connection. */ +export type ProjectSocialEdge = { + __typename?: 'ProjectSocialEdge'; + /** A cursor for use in pagination. */ + cursor?: Maybe; + /** The `ProjectSocial` at the end of the edge. */ + node?: Maybe; +}; + +/** A filter to be used against `ProjectSocial` object types. All fields are combined with a logical ‘and.’ */ +export type ProjectSocialFilter = { + /** Checks for all expressions in this list. */ + and?: InputMaybe>; + /** Filter by the object’s `createdAt` field. */ + createdAt?: InputMaybe; + /** Negates the expression. */ + not?: InputMaybe; + /** Checks for any expressions in this list. */ + or?: InputMaybe>; + /** Filter by the object’s `project` relation. */ + project?: InputMaybe; + /** Filter by the object’s `projectId` field. */ + projectId?: InputMaybe; + /** Filter by the object’s `rowId` field. */ + rowId?: InputMaybe; + /** Filter by the object’s `updatedAt` field. */ + updatedAt?: InputMaybe; + /** Filter by the object’s `url` field. */ + url?: InputMaybe; +}; + +/** Grouping methods for `ProjectSocial` for usage during aggregation. */ +export enum ProjectSocialGroupBy { + CreatedAt = 'CREATED_AT', + CreatedAtTruncatedToDay = 'CREATED_AT_TRUNCATED_TO_DAY', + CreatedAtTruncatedToHour = 'CREATED_AT_TRUNCATED_TO_HOUR', + ProjectId = 'PROJECT_ID', + UpdatedAt = 'UPDATED_AT', + UpdatedAtTruncatedToDay = 'UPDATED_AT_TRUNCATED_TO_DAY', + UpdatedAtTruncatedToHour = 'UPDATED_AT_TRUNCATED_TO_HOUR', + Url = 'URL' +} + +export type ProjectSocialHavingAverageInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingDistinctCountInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +/** Conditions for `ProjectSocial` aggregates. */ +export type ProjectSocialHavingInput = { + AND?: InputMaybe>; + OR?: InputMaybe>; + average?: InputMaybe; + distinctCount?: InputMaybe; + max?: InputMaybe; + min?: InputMaybe; + stddevPopulation?: InputMaybe; + stddevSample?: InputMaybe; + sum?: InputMaybe; + variancePopulation?: InputMaybe; + varianceSample?: InputMaybe; +}; + +export type ProjectSocialHavingMaxInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingMinInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingStddevPopulationInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingStddevSampleInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingSumInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingVariancePopulationInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingVarianceSampleInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +/** An input for mutations affecting `ProjectSocial` */ +export type ProjectSocialInput = { + createdAt?: InputMaybe; + projectId: Scalars['UUID']['input']; + rowId?: InputMaybe; + updatedAt?: InputMaybe; + url: Scalars['String']['input']; +}; + +/** Methods to use when ordering `ProjectSocial`. */ +export enum ProjectSocialOrderBy { + CreatedAtAsc = 'CREATED_AT_ASC', + CreatedAtDesc = 'CREATED_AT_DESC', + Natural = 'NATURAL', + PrimaryKeyAsc = 'PRIMARY_KEY_ASC', + PrimaryKeyDesc = 'PRIMARY_KEY_DESC', + ProjectIdAsc = 'PROJECT_ID_ASC', + ProjectIdDesc = 'PROJECT_ID_DESC', + RowIdAsc = 'ROW_ID_ASC', + RowIdDesc = 'ROW_ID_DESC', + UpdatedAtAsc = 'UPDATED_AT_ASC', + UpdatedAtDesc = 'UPDATED_AT_DESC', + UrlAsc = 'URL_ASC', + UrlDesc = 'URL_DESC' +} + +/** Represents an update to a `ProjectSocial`. Fields that are set will be updated. */ +export type ProjectSocialPatch = { + createdAt?: InputMaybe; + projectId?: InputMaybe; + rowId?: InputMaybe; + updatedAt?: InputMaybe; + url?: InputMaybe; }; /** A filter to be used against many `Post` object types. All fields are combined with a logical ‘and.’ */ @@ -3445,6 +3806,18 @@ export type ProjectToManyPostStatusFilter = { some?: InputMaybe; }; +/** A filter to be used against many `ProjectSocial` object types. All fields are combined with a logical ‘and.’ */ +export type ProjectToManyProjectSocialFilter = { + /** Aggregates across related `ProjectSocial` match the filter criteria. */ + aggregates?: InputMaybe; + /** Every related `ProjectSocial` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + every?: InputMaybe; + /** No related `ProjectSocial` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + none?: InputMaybe; + /** Some related `ProjectSocial` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + some?: InputMaybe; +}; + /** The root query type which gives access points into the data universe. */ export type Query = Node & { __typename?: 'Query'; @@ -3494,6 +3867,10 @@ export type Query = Node & { project?: Maybe; /** Get a single `Project`. */ projectBySlugAndOrganizationId?: Maybe; + /** Get a single `ProjectSocial`. */ + projectSocial?: Maybe; + /** Reads and enables pagination through a set of `ProjectSocial`. */ + projectSocials?: Maybe; /** Reads and enables pagination through a set of `Project`. */ projects?: Maybe; /** @@ -3705,6 +4082,25 @@ export type QueryProjectBySlugAndOrganizationIdArgs = { }; +/** The root query type which gives access points into the data universe. */ +export type QueryProjectSocialArgs = { + rowId: Scalars['UUID']['input']; +}; + + +/** The root query type which gives access points into the data universe. */ +export type QueryProjectSocialsArgs = { + after?: InputMaybe; + before?: InputMaybe; + condition?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; +}; + + /** The root query type which gives access points into the data universe. */ export type QueryProjectsArgs = { after?: InputMaybe; @@ -4221,6 +4617,40 @@ export type UpdateProjectPayloadProjectEdgeArgs = { orderBy?: Array; }; +/** All input for the `updateProjectSocial` mutation. */ +export type UpdateProjectSocialInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: InputMaybe; + /** An object where the defined keys will be set on the `ProjectSocial` being updated. */ + patch: ProjectSocialPatch; + rowId: Scalars['UUID']['input']; +}; + +/** The output of our update `ProjectSocial` mutation. */ +export type UpdateProjectSocialPayload = { + __typename?: 'UpdateProjectSocialPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `ProjectSocial` that was updated by this mutation. */ + projectSocial?: Maybe; + /** An edge for our `ProjectSocial`. May be used by Relay 1. */ + projectSocialEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our update `ProjectSocial` mutation. */ +export type UpdateProjectSocialPayloadProjectSocialEdgeArgs = { + orderBy?: Array; +}; + /** All input for the `updateUpvote` mutation. */ export type UpdateUpvoteInput = { /** diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index d5a7c289..f967fd1f 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -643,6 +643,39 @@ export type CreateProjectPayloadProjectEdgeArgs = { orderBy?: Array; }; +/** All input for the create `ProjectSocial` mutation. */ +export type CreateProjectSocialInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: InputMaybe; + /** The `ProjectSocial` to be created by this mutation. */ + projectSocial: ProjectSocialInput; +}; + +/** The output of our create `ProjectSocial` mutation. */ +export type CreateProjectSocialPayload = { + __typename?: 'CreateProjectSocialPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `ProjectSocial` that was created by this mutation. */ + projectSocial?: Maybe; + /** An edge for our `ProjectSocial`. May be used by Relay 1. */ + projectSocialEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our create `ProjectSocial` mutation. */ +export type CreateProjectSocialPayloadProjectSocialEdgeArgs = { + orderBy?: Array; +}; + /** All input for the create `Upvote` mutation. */ export type CreateUpvoteInput = { /** @@ -991,6 +1024,38 @@ export type DeleteProjectPayloadProjectEdgeArgs = { orderBy?: Array; }; +/** All input for the `deleteProjectSocial` mutation. */ +export type DeleteProjectSocialInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: InputMaybe; + rowId: Scalars['UUID']['input']; +}; + +/** The output of our delete `ProjectSocial` mutation. */ +export type DeleteProjectSocialPayload = { + __typename?: 'DeleteProjectSocialPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `ProjectSocial` that was deleted by this mutation. */ + projectSocial?: Maybe; + /** An edge for our `ProjectSocial`. May be used by Relay 1. */ + projectSocialEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our delete `ProjectSocial` mutation. */ +export type DeleteProjectSocialPayloadProjectSocialEdgeArgs = { + orderBy?: Array; +}; + /** All input for the `deleteUpvote` mutation. */ export type DeleteUpvoteInput = { /** @@ -1758,6 +1823,8 @@ export type Mutation = { createPostStatus?: Maybe; /** Creates a single `Project`. */ createProject?: Maybe; + /** Creates a single `ProjectSocial`. */ + createProjectSocial?: Maybe; /** Creates a single `Upvote`. */ createUpvote?: Maybe; /** Creates a single `User`. */ @@ -1778,6 +1845,8 @@ export type Mutation = { deletePostStatus?: Maybe; /** Deletes a single `Project` using a unique key. */ deleteProject?: Maybe; + /** Deletes a single `ProjectSocial` using a unique key. */ + deleteProjectSocial?: Maybe; /** Deletes a single `Upvote` using a unique key. */ deleteUpvote?: Maybe; /** Deletes a single `User` using a unique key. */ @@ -1798,6 +1867,8 @@ export type Mutation = { updatePostStatus?: Maybe; /** Updates a single `Project` using a unique key and a patch. */ updateProject?: Maybe; + /** Updates a single `ProjectSocial` using a unique key and a patch. */ + updateProjectSocial?: Maybe; /** Updates a single `Upvote` using a unique key and a patch. */ updateUpvote?: Maybe; /** Updates a single `User` using a unique key and a patch. */ @@ -1853,6 +1924,12 @@ export type MutationCreateProjectArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationCreateProjectSocialArgs = { + input: CreateProjectSocialInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationCreateUpvoteArgs = { input: CreateUpvoteInput; @@ -1913,6 +1990,12 @@ export type MutationDeleteProjectArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationDeleteProjectSocialArgs = { + input: DeleteProjectSocialInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationDeleteUpvoteArgs = { input: DeleteUpvoteInput; @@ -1973,6 +2056,12 @@ export type MutationUpdateProjectArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationUpdateProjectSocialArgs = { + input: UpdateProjectSocialInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationUpdateUpvoteArgs = { input: UpdateUpvoteInput; @@ -2274,6 +2363,8 @@ export enum OrganizationOrderBy { ProjectsDistinctCountSlugDesc = 'PROJECTS_DISTINCT_COUNT_SLUG_DESC', ProjectsDistinctCountUpdatedAtAsc = 'PROJECTS_DISTINCT_COUNT_UPDATED_AT_ASC', ProjectsDistinctCountUpdatedAtDesc = 'PROJECTS_DISTINCT_COUNT_UPDATED_AT_DESC', + ProjectsDistinctCountWebsiteAsc = 'PROJECTS_DISTINCT_COUNT_WEBSITE_ASC', + ProjectsDistinctCountWebsiteDesc = 'PROJECTS_DISTINCT_COUNT_WEBSITE_DESC', RowIdAsc = 'ROW_ID_ASC', RowIdDesc = 'ROW_ID_DESC', SlugAsc = 'SLUG_ASC', @@ -3096,9 +3187,12 @@ export type Project = { postStatuses: PostStatusConnection; /** Reads and enables pagination through a set of `Post`. */ posts: PostConnection; + /** Reads and enables pagination through a set of `ProjectSocial`. */ + projectSocials: ProjectSocialConnection; rowId: Scalars['UUID']['output']; slug: Scalars['String']['output']; updatedAt?: Maybe; + website?: Maybe; }; @@ -3125,6 +3219,18 @@ export type ProjectPostsArgs = { orderBy?: InputMaybe>; }; + +export type ProjectProjectSocialsArgs = { + after?: InputMaybe; + before?: InputMaybe; + condition?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; +}; + export type ProjectAggregates = { __typename?: 'ProjectAggregates'; /** Distinct count aggregates across the matching connection (ignoring before/after/first/last/offset) */ @@ -3158,6 +3264,8 @@ export type ProjectCondition = { slug?: InputMaybe; /** Checks for equality with the object’s `updatedAt` field. */ updatedAt?: InputMaybe; + /** Checks for equality with the object’s `website` field. */ + website?: InputMaybe; }; /** A connection to a list of `Project` values. */ @@ -3193,6 +3301,7 @@ export type ProjectDistinctCountAggregateFilter = { rowId?: InputMaybe; slug?: InputMaybe; updatedAt?: InputMaybe; + website?: InputMaybe; }; export type ProjectDistinctCountAggregates = { @@ -3213,6 +3322,8 @@ export type ProjectDistinctCountAggregates = { slug?: Maybe; /** Distinct count of updatedAt across the matching connection */ updatedAt?: Maybe; + /** Distinct count of website across the matching connection */ + website?: Maybe; }; /** A `Project` edge in the connection. */ @@ -3252,12 +3363,18 @@ export type ProjectFilter = { posts?: InputMaybe; /** Some related `posts` exist. */ postsExist?: InputMaybe; + /** Filter by the object’s `projectSocials` relation. */ + projectSocials?: InputMaybe; + /** Some related `projectSocials` exist. */ + projectSocialsExist?: InputMaybe; /** Filter by the object’s `rowId` field. */ rowId?: InputMaybe; /** Filter by the object’s `slug` field. */ slug?: InputMaybe; /** Filter by the object’s `updatedAt` field. */ updatedAt?: InputMaybe; + /** Filter by the object’s `website` field. */ + website?: InputMaybe; }; /** Grouping methods for `Project` for usage during aggregation. */ @@ -3272,7 +3389,8 @@ export enum ProjectGroupBy { Slug = 'SLUG', UpdatedAt = 'UPDATED_AT', UpdatedAtTruncatedToDay = 'UPDATED_AT_TRUNCATED_TO_DAY', - UpdatedAtTruncatedToHour = 'UPDATED_AT_TRUNCATED_TO_HOUR' + UpdatedAtTruncatedToHour = 'UPDATED_AT_TRUNCATED_TO_HOUR', + Website = 'WEBSITE' } export type ProjectHavingAverageInput = { @@ -3345,6 +3463,7 @@ export type ProjectInput = { rowId?: InputMaybe; slug: Scalars['String']['input']; updatedAt?: InputMaybe; + website?: InputMaybe; }; /** Methods to use when ordering `Project`. */ @@ -3400,12 +3519,26 @@ export enum ProjectOrderBy { PostStatusesDistinctCountUpdatedAtDesc = 'POST_STATUSES_DISTINCT_COUNT_UPDATED_AT_DESC', PrimaryKeyAsc = 'PRIMARY_KEY_ASC', PrimaryKeyDesc = 'PRIMARY_KEY_DESC', + ProjectSocialsCountAsc = 'PROJECT_SOCIALS_COUNT_ASC', + ProjectSocialsCountDesc = 'PROJECT_SOCIALS_COUNT_DESC', + ProjectSocialsDistinctCountCreatedAtAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_CREATED_AT_ASC', + ProjectSocialsDistinctCountCreatedAtDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_CREATED_AT_DESC', + ProjectSocialsDistinctCountProjectIdAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_PROJECT_ID_ASC', + ProjectSocialsDistinctCountProjectIdDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_PROJECT_ID_DESC', + ProjectSocialsDistinctCountRowIdAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_ROW_ID_ASC', + ProjectSocialsDistinctCountRowIdDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_ROW_ID_DESC', + ProjectSocialsDistinctCountUpdatedAtAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_UPDATED_AT_ASC', + ProjectSocialsDistinctCountUpdatedAtDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_UPDATED_AT_DESC', + ProjectSocialsDistinctCountUrlAsc = 'PROJECT_SOCIALS_DISTINCT_COUNT_URL_ASC', + ProjectSocialsDistinctCountUrlDesc = 'PROJECT_SOCIALS_DISTINCT_COUNT_URL_DESC', RowIdAsc = 'ROW_ID_ASC', RowIdDesc = 'ROW_ID_DESC', SlugAsc = 'SLUG_ASC', SlugDesc = 'SLUG_DESC', UpdatedAtAsc = 'UPDATED_AT_ASC', - UpdatedAtDesc = 'UPDATED_AT_DESC' + UpdatedAtDesc = 'UPDATED_AT_DESC', + WebsiteAsc = 'WEBSITE_ASC', + WebsiteDesc = 'WEBSITE_DESC' } /** Represents an update to a `Project`. Fields that are set will be updated. */ @@ -3418,6 +3551,234 @@ export type ProjectPatch = { rowId?: InputMaybe; slug?: InputMaybe; updatedAt?: InputMaybe; + website?: InputMaybe; +}; + +export type ProjectSocial = { + __typename?: 'ProjectSocial'; + createdAt?: Maybe; + /** Reads a single `Project` that is related to this `ProjectSocial`. */ + project?: Maybe; + projectId: Scalars['UUID']['output']; + rowId: Scalars['UUID']['output']; + updatedAt?: Maybe; + url: Scalars['String']['output']; +}; + +export type ProjectSocialAggregates = { + __typename?: 'ProjectSocialAggregates'; + /** Distinct count aggregates across the matching connection (ignoring before/after/first/last/offset) */ + distinctCount?: Maybe; + keys?: Maybe>>; +}; + +/** A filter to be used against aggregates of `ProjectSocial` object types. */ +export type ProjectSocialAggregatesFilter = { + /** Distinct count aggregate over matching `ProjectSocial` objects. */ + distinctCount?: InputMaybe; + /** A filter that must pass for the relevant `ProjectSocial` object to be included within the aggregate. */ + filter?: InputMaybe; +}; + +/** + * A condition to be used against `ProjectSocial` object types. All fields are + * tested for equality and combined with a logical ‘and.’ + */ +export type ProjectSocialCondition = { + /** Checks for equality with the object’s `createdAt` field. */ + createdAt?: InputMaybe; + /** Checks for equality with the object’s `projectId` field. */ + projectId?: InputMaybe; + /** Checks for equality with the object’s `rowId` field. */ + rowId?: InputMaybe; + /** Checks for equality with the object’s `updatedAt` field. */ + updatedAt?: InputMaybe; + /** Checks for equality with the object’s `url` field. */ + url?: InputMaybe; +}; + +/** A connection to a list of `ProjectSocial` values. */ +export type ProjectSocialConnection = { + __typename?: 'ProjectSocialConnection'; + /** Aggregates across the matching connection (ignoring before/after/first/last/offset) */ + aggregates?: Maybe; + /** A list of edges which contains the `ProjectSocial` and cursor to aid in pagination. */ + edges: Array>; + /** Grouped aggregates across the matching connection (ignoring before/after/first/last/offset) */ + groupedAggregates?: Maybe>; + /** A list of `ProjectSocial` objects. */ + nodes: Array>; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** The count of *all* `ProjectSocial` you could get from the connection. */ + totalCount: Scalars['Int']['output']; +}; + + +/** A connection to a list of `ProjectSocial` values. */ +export type ProjectSocialConnectionGroupedAggregatesArgs = { + groupBy: Array; + having?: InputMaybe; +}; + +export type ProjectSocialDistinctCountAggregateFilter = { + createdAt?: InputMaybe; + projectId?: InputMaybe; + rowId?: InputMaybe; + updatedAt?: InputMaybe; + url?: InputMaybe; +}; + +export type ProjectSocialDistinctCountAggregates = { + __typename?: 'ProjectSocialDistinctCountAggregates'; + /** Distinct count of createdAt across the matching connection */ + createdAt?: Maybe; + /** Distinct count of projectId across the matching connection */ + projectId?: Maybe; + /** Distinct count of rowId across the matching connection */ + rowId?: Maybe; + /** Distinct count of updatedAt across the matching connection */ + updatedAt?: Maybe; + /** Distinct count of url across the matching connection */ + url?: Maybe; +}; + +/** A `ProjectSocial` edge in the connection. */ +export type ProjectSocialEdge = { + __typename?: 'ProjectSocialEdge'; + /** A cursor for use in pagination. */ + cursor?: Maybe; + /** The `ProjectSocial` at the end of the edge. */ + node?: Maybe; +}; + +/** A filter to be used against `ProjectSocial` object types. All fields are combined with a logical ‘and.’ */ +export type ProjectSocialFilter = { + /** Checks for all expressions in this list. */ + and?: InputMaybe>; + /** Filter by the object’s `createdAt` field. */ + createdAt?: InputMaybe; + /** Negates the expression. */ + not?: InputMaybe; + /** Checks for any expressions in this list. */ + or?: InputMaybe>; + /** Filter by the object’s `project` relation. */ + project?: InputMaybe; + /** Filter by the object’s `projectId` field. */ + projectId?: InputMaybe; + /** Filter by the object’s `rowId` field. */ + rowId?: InputMaybe; + /** Filter by the object’s `updatedAt` field. */ + updatedAt?: InputMaybe; + /** Filter by the object’s `url` field. */ + url?: InputMaybe; +}; + +/** Grouping methods for `ProjectSocial` for usage during aggregation. */ +export enum ProjectSocialGroupBy { + CreatedAt = 'CREATED_AT', + CreatedAtTruncatedToDay = 'CREATED_AT_TRUNCATED_TO_DAY', + CreatedAtTruncatedToHour = 'CREATED_AT_TRUNCATED_TO_HOUR', + ProjectId = 'PROJECT_ID', + UpdatedAt = 'UPDATED_AT', + UpdatedAtTruncatedToDay = 'UPDATED_AT_TRUNCATED_TO_DAY', + UpdatedAtTruncatedToHour = 'UPDATED_AT_TRUNCATED_TO_HOUR', + Url = 'URL' +} + +export type ProjectSocialHavingAverageInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingDistinctCountInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +/** Conditions for `ProjectSocial` aggregates. */ +export type ProjectSocialHavingInput = { + AND?: InputMaybe>; + OR?: InputMaybe>; + average?: InputMaybe; + distinctCount?: InputMaybe; + max?: InputMaybe; + min?: InputMaybe; + stddevPopulation?: InputMaybe; + stddevSample?: InputMaybe; + sum?: InputMaybe; + variancePopulation?: InputMaybe; + varianceSample?: InputMaybe; +}; + +export type ProjectSocialHavingMaxInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingMinInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingStddevPopulationInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingStddevSampleInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingSumInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingVariancePopulationInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type ProjectSocialHavingVarianceSampleInput = { + createdAt?: InputMaybe; + updatedAt?: InputMaybe; +}; + +/** An input for mutations affecting `ProjectSocial` */ +export type ProjectSocialInput = { + createdAt?: InputMaybe; + projectId: Scalars['UUID']['input']; + rowId?: InputMaybe; + updatedAt?: InputMaybe; + url: Scalars['String']['input']; +}; + +/** Methods to use when ordering `ProjectSocial`. */ +export enum ProjectSocialOrderBy { + CreatedAtAsc = 'CREATED_AT_ASC', + CreatedAtDesc = 'CREATED_AT_DESC', + Natural = 'NATURAL', + PrimaryKeyAsc = 'PRIMARY_KEY_ASC', + PrimaryKeyDesc = 'PRIMARY_KEY_DESC', + ProjectIdAsc = 'PROJECT_ID_ASC', + ProjectIdDesc = 'PROJECT_ID_DESC', + RowIdAsc = 'ROW_ID_ASC', + RowIdDesc = 'ROW_ID_DESC', + UpdatedAtAsc = 'UPDATED_AT_ASC', + UpdatedAtDesc = 'UPDATED_AT_DESC', + UrlAsc = 'URL_ASC', + UrlDesc = 'URL_DESC' +} + +/** Represents an update to a `ProjectSocial`. Fields that are set will be updated. */ +export type ProjectSocialPatch = { + createdAt?: InputMaybe; + projectId?: InputMaybe; + rowId?: InputMaybe; + updatedAt?: InputMaybe; + url?: InputMaybe; }; /** A filter to be used against many `Post` object types. All fields are combined with a logical ‘and.’ */ @@ -3444,6 +3805,18 @@ export type ProjectToManyPostStatusFilter = { some?: InputMaybe; }; +/** A filter to be used against many `ProjectSocial` object types. All fields are combined with a logical ‘and.’ */ +export type ProjectToManyProjectSocialFilter = { + /** Aggregates across related `ProjectSocial` match the filter criteria. */ + aggregates?: InputMaybe; + /** Every related `ProjectSocial` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + every?: InputMaybe; + /** No related `ProjectSocial` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + none?: InputMaybe; + /** Some related `ProjectSocial` matches the filter criteria. All fields are combined with a logical ‘and.’ */ + some?: InputMaybe; +}; + /** The root query type which gives access points into the data universe. */ export type Query = Node & { __typename?: 'Query'; @@ -3493,6 +3866,10 @@ export type Query = Node & { project?: Maybe; /** Get a single `Project`. */ projectBySlugAndOrganizationId?: Maybe; + /** Get a single `ProjectSocial`. */ + projectSocial?: Maybe; + /** Reads and enables pagination through a set of `ProjectSocial`. */ + projectSocials?: Maybe; /** Reads and enables pagination through a set of `Project`. */ projects?: Maybe; /** @@ -3704,6 +4081,25 @@ export type QueryProjectBySlugAndOrganizationIdArgs = { }; +/** The root query type which gives access points into the data universe. */ +export type QueryProjectSocialArgs = { + rowId: Scalars['UUID']['input']; +}; + + +/** The root query type which gives access points into the data universe. */ +export type QueryProjectSocialsArgs = { + after?: InputMaybe; + before?: InputMaybe; + condition?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; + offset?: InputMaybe; + orderBy?: InputMaybe>; +}; + + /** The root query type which gives access points into the data universe. */ export type QueryProjectsArgs = { after?: InputMaybe; @@ -4220,6 +4616,40 @@ export type UpdateProjectPayloadProjectEdgeArgs = { orderBy?: Array; }; +/** All input for the `updateProjectSocial` mutation. */ +export type UpdateProjectSocialInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: InputMaybe; + /** An object where the defined keys will be set on the `ProjectSocial` being updated. */ + patch: ProjectSocialPatch; + rowId: Scalars['UUID']['input']; +}; + +/** The output of our update `ProjectSocial` mutation. */ +export type UpdateProjectSocialPayload = { + __typename?: 'UpdateProjectSocialPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `ProjectSocial` that was updated by this mutation. */ + projectSocial?: Maybe; + /** An edge for our `ProjectSocial`. May be used by Relay 1. */ + projectSocialEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our update `ProjectSocial` mutation. */ +export type UpdateProjectSocialPayloadProjectSocialEdgeArgs = { + orderBy?: Array; +}; + /** All input for the `updateUpvote` mutation. */ export type UpdateUpvoteInput = { /** diff --git a/src/lib/actions/getOrganizations.ts b/src/lib/actions/getOrganizations.ts deleted file mode 100644 index a25bab6e..00000000 --- a/src/lib/actions/getOrganizations.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { cache } from "react"; - -import { getSdk } from "lib/graphql"; -import { getAuthSession } from "lib/util"; - -import { Role } from "generated/graphql"; - -/** - * Helper function to fetch an organization's details. Cached for deduping requests. - */ -const getOrganizations = cache(async () => { - const session = await getAuthSession(); - - if (!session) return null; - - const sdk = getSdk({ session }); - - const { organizations } = await sdk.Organizations({ - userId: session.user?.rowId!, - isMember: true, - excludeRoles: [Role.Member], - }); - - if (!organizations) return null; - - return organizations?.nodes; -}); - -export default getOrganizations; diff --git a/src/lib/actions/index.ts b/src/lib/actions/index.ts index 151c67c1..dccfcb82 100644 --- a/src/lib/actions/index.ts +++ b/src/lib/actions/index.ts @@ -1,7 +1,6 @@ export { default as getCustomer } from "./getCustomer"; export { default as getFeedback } from "./getFeedback"; export { default as getOrganization } from "./getOrganization"; -export { default as getOrganizations } from "./getOrganizations"; export { default as getOwnerTier } from "./getOwnerTier"; export { default as getProduct } from "./getProduct"; export { default as getProject } from "./getProject"; From 6c4f9978daf40e1fba17be017efeff7931244a78 Mon Sep 17 00:00:00 2001 From: Beau Hawkinson <72956780+Twonarly1@users.noreply.github.com> Date: Thu, 22 May 2025 08:37:54 -0500 Subject: [PATCH 7/7] refactor: use children as breadcrumb prop and iterate on that for nested and sub items --- .../[projectSlug]/[feedbackId]/page.tsx | 21 ++++------ .../projects/[projectSlug]/page.tsx | 25 +++++------ .../core/breadcrumb/Breadcrumb/Breadcrumb.tsx | 42 +++++++------------ .../BreadcrumbDropdown/BreadcrumbDropdown.tsx | 42 +++++++++---------- src/generated/graphql.mock.ts | 2 +- src/generated/graphql.sdk.ts | 9 ++-- src/generated/graphql.ts | 9 ++-- src/lib/actions/getOrganizationProjects.ts | 32 ++++++++++++++ src/lib/actions/index.ts | 1 + .../graphql/queries/projects.query.graphql | 6 ++- 10 files changed, 103 insertions(+), 86 deletions(-) create mode 100644 src/lib/actions/getOrganizationProjects.ts diff --git a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx index 853226c5..3238a9c1 100644 --- a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx +++ b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/[feedbackId]/page.tsx @@ -10,7 +10,7 @@ import { useInfiniteCommentsQuery, useOrganizationRoleQuery, } from "generated/graphql"; -import { getFeedback, getOrganization } from "lib/actions"; +import { getFeedback, getOrganizationProjects } from "lib/actions"; import { app } from "lib/config"; import { freeTierCommentsOptions } from "lib/options"; import { getQueryClient } from "lib/util"; @@ -40,12 +40,12 @@ const FeedbackPage = async ({ params }: Props) => { if (!session) notFound(); - const [organization, feedback] = await Promise.all([ - getOrganization({ organizationSlug }), + const [organizationProjects, feedback] = await Promise.all([ + getOrganizationProjects({ organizationSlug }), getFeedback({ feedbackId }), ]); - const numberOfProjects = organization?.projects?.nodes?.length ?? 0; + const numberOfProjects = organizationProjects?.nodes?.length ?? 0; if (!feedback) notFound(); @@ -67,19 +67,12 @@ const FeedbackPage = async ({ params }: Props) => { { label: feedback?.project?.name ?? projectSlug, href: - numberOfProjects <= 2 + numberOfProjects === 1 ? `/organizations/${organizationSlug}/projects/${projectSlug}` : undefined, - subItems: + children: numberOfProjects > 1 - ? organization?.projects?.nodes.map((project) => ({ - label: project!.name, - href: `/organizations/${organizationSlug}/projects/${project!.slug}`, - })) - : undefined, - nestedSubItems: - numberOfProjects > 1 - ? organization?.projects?.nodes.map((project) => ({ + ? organizationProjects?.nodes.map((project) => ({ label: project!.name, href: `/organizations/${organizationSlug}/projects/${project!.slug}`, })) diff --git a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx index 215580c4..a33515dd 100644 --- a/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx +++ b/src/app/organizations/[organizationSlug]/projects/[projectSlug]/page.tsx @@ -17,7 +17,7 @@ import { useProjectStatusesQuery, useStatusBreakdownQuery, } from "generated/graphql"; -import { getOrganization, getProject } from "lib/actions"; +import { getOrganizationProjects, getProject } from "lib/actions"; import { app } from "lib/config"; import { getSdk } from "lib/graphql"; import { freeTierFeedbackOptions } from "lib/options"; @@ -41,7 +41,10 @@ export const generateMetadata = async ({ params }: Props) => { interface Props { /** Project page params. */ - params: Promise<{ organizationSlug: string; projectSlug: string }>; + params: Promise<{ + organizationSlug: string; + projectSlug: string; + }>; /** Projects page search params. */ searchParams: Promise; } @@ -56,12 +59,12 @@ const ProjectPage = async ({ params, searchParams }: Props) => { if (!session) notFound(); - const [project, organization] = await Promise.all([ + const [project, organizationProjects] = await Promise.all([ getProject({ organizationSlug, projectSlug }), - getOrganization({ organizationSlug }), + getOrganizationProjects({ organizationSlug, excludeProjects: projectSlug }), ]); - if (!project || !organization) notFound(); + if (!project || !organizationProjects) notFound(); const sdk = getSdk({ session }); @@ -90,13 +93,11 @@ const ProjectPage = async ({ params, searchParams }: Props) => { }, { label: project.name ?? projectSlug, - subItems: organization?.projects?.nodes?.length - ? organization?.projects?.nodes - .filter((p) => p?.slug !== projectSlug) - .map((project) => ({ - label: project!.name, - href: `/organizations/${organizationSlug}/projects/${project!.slug}`, - })) + children: organizationProjects?.nodes?.length + ? organizationProjects?.nodes.map((project) => ({ + label: project!.name, + href: `/organizations/${organizationSlug}/projects/${project!.slug}`, + })) : undefined, }, ]; diff --git a/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx b/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx index 6828c0fa..7ce55eb6 100644 --- a/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx +++ b/src/components/core/breadcrumb/Breadcrumb/Breadcrumb.tsx @@ -13,29 +13,14 @@ const sharedIconStyles = { mx: 1.5, }; -interface NestedItemRecord { - /** Label for the sub-item. */ - label: string; - /** URL path the sub-item navigates to. */ - href: `/${string}`; -} - -interface SubItemRecord { - /** Label for the sub-item. */ - label: string; - /** URL path the sub-item navigates to. */ - href: `/${string}`; -} - export interface BreadcrumbRecord { - /** Label for the breadcrumb. */ label: string; - /** URL path the breadcrumb navigates to. */ href?: `/${string}`; - /** Sub-items for the breadcrumb. */ - subItems?: SubItemRecord[]; - /** Nested sub-items for the breadcrumb. */ - nestedSubItems?: NestedItemRecord[]; + /** Sub-items or nested dropdown items (unified) */ + children?: { + label: string; + href?: `/${string}`; + }[]; } interface Props { @@ -91,7 +76,7 @@ const Breadcrumb = ({ breadcrumbs }: Props) => { )} - {lastItem.subItems?.length ? ( + {lastItem.children?.length ? ( @@ -102,7 +87,7 @@ const Breadcrumb = ({ breadcrumbs }: Props) => { /> } - breadcrumbs={lastItem.subItems} + breadcrumbs={lastItem.children} /> ) : ( @@ -111,15 +96,18 @@ const Breadcrumb = ({ breadcrumbs }: Props) => { {/* Large viewport */} - {breadcrumbs.map(({ label, href, subItems }, index) => { + {breadcrumbs.map(({ label, href, children }, index) => { const isLastItem = breadcrumbs.length - 1 === index; return ( - // biome-ignore lint/suspicious/noArrayIndexKey: index used in the key in case an organization and project have the same label - + + key={`${label}-${index}`} + align="center" + > - {subItems?.length ? ( + {children?.length ? ( @@ -130,7 +118,7 @@ const Breadcrumb = ({ breadcrumbs }: Props) => { /> } - breadcrumbs={subItems} + breadcrumbs={children} /> ) : href ? ( diff --git a/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx b/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx index 74e4dffe..040c3043 100644 --- a/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx +++ b/src/components/core/breadcrumb/BreadcrumbDropdown/BreadcrumbDropdown.tsx @@ -7,10 +7,10 @@ import { Flex, } from "@omnidev/sigil"; import { LuChevronRight } from "react-icons/lu"; +import { useRouter } from "next/navigation"; import type { BreadcrumbRecord } from "components/core/breadcrumb"; import type { MenuProps } from "@omnidev/sigil"; -import { useRouter } from "next/navigation"; interface Props extends MenuProps { /** Array of navigation breadcrumbs. */ @@ -26,10 +26,13 @@ const BreadcrumbDropdown = ({ breadcrumbs, trigger, ...rest }: Props) => { return ( - {breadcrumbs.map(({ label, href, nestedSubItems }, index) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: - - {nestedSubItems?.length ? ( + {breadcrumbs.map(({ label, href, children }, index) => ( + + key={`${label}-${index}`} + w="full" + > + {children?.length ? ( { justifyContent="space-between" > {label} - { } > - - {nestedSubItems.map( - ({ label: nestedLabel, href: nestedHref }) => ( - nestedHref && router.push(nestedHref)} - > - - {nestedLabel} - - - ), - )} + + {children.map(({ label: childLabel, href: childHref }) => ( + childHref && router.push(childHref)} + > + + {childLabel} + + + ))} ) : ( href && router.push(href)} > diff --git a/src/generated/graphql.mock.ts b/src/generated/graphql.mock.ts index a4e2ee62..9f4937c1 100644 --- a/src/generated/graphql.mock.ts +++ b/src/generated/graphql.mock.ts @@ -868,7 +868,7 @@ export const mockProjectStatusesQuery = (resolver: GraphQLResponseResolver { - * const { pageSize, offset, organizationSlug, search } = variables; + * const { pageSize, offset, organizationSlug, search, excludeProjects } = variables; * return HttpResponse.json({ * data: { projects } * }) diff --git a/src/generated/graphql.sdk.ts b/src/generated/graphql.sdk.ts index 4c030600..4dcb2d59 100644 --- a/src/generated/graphql.sdk.ts +++ b/src/generated/graphql.sdk.ts @@ -5737,10 +5737,11 @@ export type ProjectStatusesQueryVariables = Exact<{ export type ProjectStatusesQuery = { __typename?: 'Query', postStatuses?: { __typename?: 'PostStatusConnection', nodes: Array<{ __typename?: 'PostStatus', rowId: string, status: string, description?: string | null, color?: string | null, isDefault: boolean } | null> } | null }; export type ProjectsQueryVariables = Exact<{ - pageSize: Scalars['Int']['input']; - offset: Scalars['Int']['input']; + pageSize?: InputMaybe; + offset?: InputMaybe; organizationSlug: Scalars['String']['input']; search?: InputMaybe; + excludeProjects?: InputMaybe | Scalars['String']['input']>; }>; @@ -6368,12 +6369,12 @@ export const ProjectStatusesDocument = gql` } `; export const ProjectsDocument = gql` - query Projects($pageSize: Int!, $offset: Int!, $organizationSlug: String!, $search: String) { + query Projects($pageSize: Int, $offset: Int, $organizationSlug: String!, $search: String, $excludeProjects: [String!]) { projects( orderBy: POSTS_COUNT_DESC first: $pageSize offset: $offset - filter: {name: {includesInsensitive: $search}, organization: {slug: {equalTo: $organizationSlug}}} + filter: {name: {includesInsensitive: $search}, organization: {slug: {equalTo: $organizationSlug}}, slug: {notIn: $excludeProjects}} ) { totalCount nodes { diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index f967fd1f..d3e6aa48 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -5736,10 +5736,11 @@ export type ProjectStatusesQueryVariables = Exact<{ export type ProjectStatusesQuery = { __typename?: 'Query', postStatuses?: { __typename?: 'PostStatusConnection', nodes: Array<{ __typename?: 'PostStatus', rowId: string, status: string, description?: string | null, color?: string | null, isDefault: boolean } | null> } | null }; export type ProjectsQueryVariables = Exact<{ - pageSize: Scalars['Int']['input']; - offset: Scalars['Int']['input']; + pageSize?: InputMaybe; + offset?: InputMaybe; organizationSlug: Scalars['String']['input']; search?: InputMaybe; + excludeProjects?: InputMaybe | Scalars['String']['input']>; }>; @@ -7445,12 +7446,12 @@ useInfiniteProjectStatusesQuery.getKey = (variables: ProjectStatusesQueryVariabl useProjectStatusesQuery.fetcher = (variables: ProjectStatusesQueryVariables, options?: RequestInit['headers']) => graphqlFetch(ProjectStatusesDocument, variables, options); export const ProjectsDocument = ` - query Projects($pageSize: Int!, $offset: Int!, $organizationSlug: String!, $search: String) { + query Projects($pageSize: Int, $offset: Int, $organizationSlug: String!, $search: String, $excludeProjects: [String!]) { projects( orderBy: POSTS_COUNT_DESC first: $pageSize offset: $offset - filter: {name: {includesInsensitive: $search}, organization: {slug: {equalTo: $organizationSlug}}} + filter: {name: {includesInsensitive: $search}, organization: {slug: {equalTo: $organizationSlug}}, slug: {notIn: $excludeProjects}} ) { totalCount nodes { diff --git a/src/lib/actions/getOrganizationProjects.ts b/src/lib/actions/getOrganizationProjects.ts new file mode 100644 index 00000000..46aecfea --- /dev/null +++ b/src/lib/actions/getOrganizationProjects.ts @@ -0,0 +1,32 @@ +import { getSdk } from "lib/graphql"; +import { getAuthSession } from "lib/util"; +import type { Organization } from "generated/graphql"; + +const PAGE_SIZE = 6; + +interface OrganizationOptions { + /** Organization slug. */ + organizationSlug: Organization["slug"]; + /** Exclude projects. */ + excludeProjects?: string; +} + +const getOrganizationProjects = async ({ + organizationSlug, + excludeProjects, +}: OrganizationOptions) => { + const session = await getAuthSession(); + + if (!session) return null; + + const sdk = getSdk({ session }); + + const { projects: organizationProjects } = await sdk.Projects({ + organizationSlug: organizationSlug!, + excludeProjects, + }); + + return organizationProjects; +}; + +export default getOrganizationProjects; diff --git a/src/lib/actions/index.ts b/src/lib/actions/index.ts index dccfcb82..db01158d 100644 --- a/src/lib/actions/index.ts +++ b/src/lib/actions/index.ts @@ -1,6 +1,7 @@ export { default as getCustomer } from "./getCustomer"; export { default as getFeedback } from "./getFeedback"; export { default as getOrganization } from "./getOrganization"; +export { default as getOrganizationProjects } from "./getOrganizationProjects"; export { default as getOwnerTier } from "./getOwnerTier"; export { default as getProduct } from "./getProduct"; export { default as getProject } from "./getProject"; diff --git a/src/lib/graphql/queries/projects.query.graphql b/src/lib/graphql/queries/projects.query.graphql index d0b5b803..735b98ad 100644 --- a/src/lib/graphql/queries/projects.query.graphql +++ b/src/lib/graphql/queries/projects.query.graphql @@ -1,8 +1,9 @@ query Projects( - $pageSize: Int! - $offset: Int! + $pageSize: Int + $offset: Int $organizationSlug: String! $search: String + $excludeProjects: [String!] ) { projects( orderBy: POSTS_COUNT_DESC @@ -11,6 +12,7 @@ query Projects( filter: { name: { includesInsensitive: $search } organization: { slug: { equalTo: $organizationSlug } } + slug: { notIn: $excludeProjects } } ) { totalCount