From 483a7186294461bea662d20ab7a94b69ef8aab81 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Fri, 27 Dec 2024 13:44:41 -0600 Subject: [PATCH 01/72] refactor(auth): mock user ID temporarily --- .env.local.template | 4 ++++ src/lib/config/env.config.ts | 3 +++ src/lib/hooks/useAuth.tsx | 5 ++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.env.local.template b/.env.local.template index ba7796d69..3f0cf5d1a 100644 --- a/.env.local.template +++ b/.env.local.template @@ -5,3 +5,7 @@ WALLETCONNECT_PROJECT_ID="" AUTH_KEYCLOAK_SECRET="" # NB: an auth secret can be generated and injected automatically with `npx auth`, see https://cli.authjs.dev AUTH_SECRET="" + +# NB: mocked user ID from locally seeded database. +# TODO: remove once keycloak / hidra are synced with database +NEXT_PUBLIC_MOCK_USER_ID="" diff --git a/src/lib/config/env.config.ts b/src/lib/config/env.config.ts index 9df00e351..564e83b1f 100644 --- a/src/lib/config/env.config.ts +++ b/src/lib/config/env.config.ts @@ -5,3 +5,6 @@ export const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL; // web3 export const WALLETCONNECT_PROJECT_ID = process.env.WALLETCONNECT_PROJECT_ID; + +// TODO: remove once keycloak / hidra are synced with database +export const MOCK_USER_ID = process.env.NEXT_PUBLIC_MOCK_USER_ID; diff --git a/src/lib/hooks/useAuth.tsx b/src/lib/hooks/useAuth.tsx index 0c2b5d571..26d52a06f 100644 --- a/src/lib/hooks/useAuth.tsx +++ b/src/lib/hooks/useAuth.tsx @@ -1,5 +1,7 @@ import { useSession } from "next-auth/react"; +import { MOCK_USER_ID } from "lib/config"; + /** * Access authentication state and user data. */ @@ -9,7 +11,8 @@ const useAuth = () => { return { isAuthenticated: status === "authenticated", isLoading: status === "loading", - user: data?.user, + // ! NB: mock user ID from seeded database until we sync with hidra + user: { ...data?.user, id: MOCK_USER_ID }, expiresAt: data?.expires, update, }; From ff60aa6263550e75d62d3ff5e8a1c4a251f61791 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Fri, 27 Dec 2024 21:57:35 -0600 Subject: [PATCH 02/72] feature(dashboard): query for feedback aggregate --- .../dashboard/Aggregate/Aggregate.tsx | 6 -- .../dashboard/DashboardPage/DashboardPage.tsx | 13 ++++- src/generated/graphql.ts | 56 +++++++++++++++++++ .../queries/dashboardAggregates.query.graphql | 13 +++++ 4 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 src/lib/graphql/queries/dashboardAggregates.query.graphql diff --git a/src/components/dashboard/Aggregate/Aggregate.tsx b/src/components/dashboard/Aggregate/Aggregate.tsx index 88381aafd..4fe546340 100644 --- a/src/components/dashboard/Aggregate/Aggregate.tsx +++ b/src/components/dashboard/Aggregate/Aggregate.tsx @@ -44,12 +44,6 @@ const Aggregate = ({ title, value, icon, isLoaded = true, isError }: Props) => ( {isError ? "Error" : value} - - - - {isError ? "Error" : "+4.2069% from last month"} - - ); diff --git a/src/components/dashboard/DashboardPage/DashboardPage.tsx b/src/components/dashboard/DashboardPage/DashboardPage.tsx index ed009ac7c..013b9f122 100644 --- a/src/components/dashboard/DashboardPage/DashboardPage.tsx +++ b/src/components/dashboard/DashboardPage/DashboardPage.tsx @@ -10,6 +10,7 @@ import { LuPlusCircle } from "react-icons/lu"; import { Aggregate, Feedback, PinnedOrganizations } from "components/dashboard"; import { Page } from "components/layout"; +import { useDashboardAggregatesQuery } from "generated/graphql"; import { app } from "lib/config"; import { useAuth, useDataState } from "lib/hooks"; @@ -20,10 +21,20 @@ const DashboardPage = () => { const { user } = useAuth(), { isLoading, isError } = useDataState({ timeout: 400 }); + const { data: feedbackCount } = useDashboardAggregatesQuery( + { + userId: user?.id!, + }, + { + enabled: !!user, + select: (data) => data?.posts?.totalCount, + } + ); + const aggregates = [ { title: app.dashboardPage.aggregates.totalFeedback.title, - value: "12,345", + value: feedbackCount ?? 0, icon: HiOutlineChatBubbleLeftRight, }, { diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index a02477f61..fd80a255a 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -3287,6 +3287,13 @@ export type UpvotePostMutationVariables = Exact<{ export type UpvotePostMutation = { __typename?: 'Mutation', createUpvote?: { __typename?: 'CreateUpvotePayload', clientMutationId?: string | null } | null }; +export type DashboardAggregatesQueryVariables = Exact<{ + userId: Scalars['UUID']['input']; +}>; + + +export type DashboardAggregatesQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', totalCount: number } | null }; + export type OrganizationQueryVariables = Exact<{ slug: Scalars['String']['input']; }>; @@ -3478,6 +3485,55 @@ export const useUpvotePostMutation = < } )}; +export const DashboardAggregatesDocument = ` + query DashboardAggregates($userId: UUID!) { + posts( + filter: {project: {organization: {userOrganizations: {some: {userId: {equalTo: $userId}}}}}} + ) { + totalCount + } +} + `; + +export const useDashboardAggregatesQuery = < + TData = DashboardAggregatesQuery, + TError = unknown + >( + variables: DashboardAggregatesQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } + ) => { + + return useQuery( + { + queryKey: ['DashboardAggregates', variables], + queryFn: useGraphqlClient(DashboardAggregatesDocument).bind(null, variables), + ...options + } + )}; + +useDashboardAggregatesQuery.getKey = (variables: DashboardAggregatesQueryVariables) => ['DashboardAggregates', variables]; + +export const useInfiniteDashboardAggregatesQuery = < + TData = InfiniteData, + TError = unknown + >( + variables: DashboardAggregatesQueryVariables, + options: Omit, 'queryKey'> & { queryKey?: UseInfiniteQueryOptions['queryKey'] } + ) => { + const query = useGraphqlClient(DashboardAggregatesDocument) + return useInfiniteQuery( + (() => { + const { queryKey: optionsQueryKey, ...restOptions } = options; + return { + queryKey: optionsQueryKey ?? ['DashboardAggregates.infinite', variables], + queryFn: (metaData) => query({...variables, ...(metaData.pageParam ?? {})}), + ...restOptions + } + })() + )}; + +useInfiniteDashboardAggregatesQuery.getKey = (variables: DashboardAggregatesQueryVariables) => ['DashboardAggregates.infinite', variables]; + export const OrganizationDocument = ` query Organization($slug: String!) { organizationBySlug(slug: $slug) { diff --git a/src/lib/graphql/queries/dashboardAggregates.query.graphql b/src/lib/graphql/queries/dashboardAggregates.query.graphql new file mode 100644 index 000000000..05277b430 --- /dev/null +++ b/src/lib/graphql/queries/dashboardAggregates.query.graphql @@ -0,0 +1,13 @@ +query DashboardAggregates($userId: UUID!) { + posts( + filter: { + project: { + organization: { + userOrganizations: { some: { userId: { equalTo: $userId } } } + } + } + } + ) { + totalCount + } +} From c1d29a5247a0f2e0ecdbb149e763fc4aca1bab55 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Fri, 27 Dec 2024 22:38:31 -0600 Subject: [PATCH 03/72] feature(dashboard): query for active users aggregate --- .../dashboard/DashboardPage/DashboardPage.tsx | 11 +++++++---- src/generated/graphql.ts | 7 ++++++- .../queries/dashboardAggregates.query.graphql | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/components/dashboard/DashboardPage/DashboardPage.tsx b/src/components/dashboard/DashboardPage/DashboardPage.tsx index 013b9f122..c50a5b2ac 100644 --- a/src/components/dashboard/DashboardPage/DashboardPage.tsx +++ b/src/components/dashboard/DashboardPage/DashboardPage.tsx @@ -21,25 +21,28 @@ const DashboardPage = () => { const { user } = useAuth(), { isLoading, isError } = useDataState({ timeout: 400 }); - const { data: feedbackCount } = useDashboardAggregatesQuery( + const { data: dashboardAggregates } = useDashboardAggregatesQuery( { userId: user?.id!, }, { enabled: !!user, - select: (data) => data?.posts?.totalCount, + select: (data) => ({ + totalFeedback: data?.posts?.totalCount, + totalUsers: data?.users?.totalCount, + }), } ); const aggregates = [ { title: app.dashboardPage.aggregates.totalFeedback.title, - value: feedbackCount ?? 0, + value: dashboardAggregates?.totalFeedback ?? 0, icon: HiOutlineChatBubbleLeftRight, }, { title: app.dashboardPage.aggregates.activeUsers.title, - value: "42,069", + value: dashboardAggregates?.totalUsers ?? 0, icon: HiOutlineUserGroup, }, { diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index fd80a255a..d795eb5d4 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -3292,7 +3292,7 @@ export type DashboardAggregatesQueryVariables = Exact<{ }>; -export type DashboardAggregatesQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', totalCount: number } | null }; +export type DashboardAggregatesQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', totalCount: number } | null, users?: { __typename?: 'UserConnection', totalCount: number } | null }; export type OrganizationQueryVariables = Exact<{ slug: Scalars['String']['input']; @@ -3492,6 +3492,11 @@ export const DashboardAggregatesDocument = ` ) { totalCount } + users( + filter: {userOrganizations: {some: {organization: {userOrganizations: {some: {userId: {equalTo: $userId}}}}}}} + ) { + totalCount + } } `; diff --git a/src/lib/graphql/queries/dashboardAggregates.query.graphql b/src/lib/graphql/queries/dashboardAggregates.query.graphql index 05277b430..e845bee85 100644 --- a/src/lib/graphql/queries/dashboardAggregates.query.graphql +++ b/src/lib/graphql/queries/dashboardAggregates.query.graphql @@ -10,4 +10,18 @@ query DashboardAggregates($userId: UUID!) { ) { totalCount } + # TODO: discuss a better approach. Aggregate data with the plugin is difficult across M2M relationships. This is a temporary solution. + users( + filter: { + userOrganizations: { + some: { + organization: { + userOrganizations: { some: { userId: { equalTo: $userId } } } + } + } + } + } + ) { + totalCount + } } From 8face19bbf9abd371bea2f12bcb2e90be658a39b Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Fri, 27 Dec 2024 22:58:48 -0600 Subject: [PATCH 04/72] refactor(organizations): update queries to user userId variable --- .../PinnedOrganizations/PinnedOrganizations.tsx | 15 ++++++++++++++- .../OrganizationList/OrganizationList.tsx | 6 +++++- src/generated/graphql.ts | 15 ++++++++------- .../graphql/queries/organizations.query.graphql | 6 +++++- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/components/dashboard/PinnedOrganizations/PinnedOrganizations.tsx b/src/components/dashboard/PinnedOrganizations/PinnedOrganizations.tsx index 6b18e9eb8..c3a10f8fe 100644 --- a/src/components/dashboard/PinnedOrganizations/PinnedOrganizations.tsx +++ b/src/components/dashboard/PinnedOrganizations/PinnedOrganizations.tsx @@ -9,6 +9,7 @@ import { OrganizationCard } from "components/dashboard"; import { ErrorBoundary } from "components/layout"; import { OrganizationOrderBy, useOrganizationsQuery } from "generated/graphql"; import { app } from "lib/config"; +import { useAuth } from "lib/hooks"; import type { Organization } from "generated/graphql"; @@ -17,6 +18,7 @@ import type { Organization } from "generated/graphql"; */ const PinnedOrganizations = () => { const router = useRouter(); + const { user } = useAuth(); const { data: pinnedOrganizations, @@ -26,8 +28,10 @@ const PinnedOrganizations = () => { { first: 3, orderBy: [OrganizationOrderBy.UserOrganizationsCountDesc], + userId: user?.id!, }, { + enabled: !!user, select: (data) => data?.organizations?.nodes, } ); @@ -76,7 +80,16 @@ const PinnedOrganizations = () => { {isError ? ( ) : ( - + {isLoading ? ( ) : ( diff --git a/src/components/organization/OrganizationList/OrganizationList.tsx b/src/components/organization/OrganizationList/OrganizationList.tsx index bcfd2506b..461b822f3 100644 --- a/src/components/organization/OrganizationList/OrganizationList.tsx +++ b/src/components/organization/OrganizationList/OrganizationList.tsx @@ -7,7 +7,7 @@ import { SkeletonArray } from "components/core"; import { ErrorBoundary } from "components/layout"; import { OrganizationListItem } from "components/organization"; import { OrganizationOrderBy, useOrganizationsQuery } from "generated/graphql"; -import { useDebounceValue, useSearchParams } from "lib/hooks"; +import { useAuth, useDebounceValue, useSearchParams } from "lib/hooks"; import type { StackProps } from "@omnidev/sigil"; import type { Organization } from "generated/graphql"; @@ -21,6 +21,8 @@ const OrganizationList = ({ ...props }: StackProps) => { const [debouncedSearch] = useDebounceValue({ value: search }); + const { user } = useAuth(); + const { data: organizations, isLoading, @@ -28,9 +30,11 @@ const OrganizationList = ({ ...props }: StackProps) => { } = useOrganizationsQuery( { orderBy: [OrganizationOrderBy.UserOrganizationsCountDesc], + userId: user?.id!, search: debouncedSearch, }, { + enabled: !!user, placeholderData: keepPreviousData, select: (data) => data?.organizations?.nodes, } diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index d795eb5d4..364bdc567 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -3304,6 +3304,7 @@ export type OrganizationQuery = { __typename?: 'Query', organizationBySlug?: { _ export type OrganizationsQueryVariables = Exact<{ first?: InputMaybe; orderBy?: InputMaybe | OrganizationOrderBy>; + userId: Scalars['UUID']['input']; search?: InputMaybe; }>; @@ -3589,11 +3590,11 @@ export const useInfiniteOrganizationQuery = < useInfiniteOrganizationQuery.getKey = (variables: OrganizationQueryVariables) => ['Organization.infinite', variables]; export const OrganizationsDocument = ` - query Organizations($first: Int, $orderBy: [OrganizationOrderBy!], $search: String) { + query Organizations($first: Int, $orderBy: [OrganizationOrderBy!], $userId: UUID!, $search: String) { organizations( first: $first orderBy: $orderBy - filter: {name: {includesInsensitive: $search}} + filter: {name: {includesInsensitive: $search}, userOrganizations: {some: {userId: {equalTo: $userId}}}} ) { nodes { rowId @@ -3613,19 +3614,19 @@ export const useOrganizationsQuery = < TData = OrganizationsQuery, TError = unknown >( - variables?: OrganizationsQueryVariables, + variables: OrganizationsQueryVariables, options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } ) => { return useQuery( { - queryKey: variables === undefined ? ['Organizations'] : ['Organizations', variables], + queryKey: ['Organizations', variables], queryFn: useGraphqlClient(OrganizationsDocument).bind(null, variables), ...options } )}; -useOrganizationsQuery.getKey = (variables?: OrganizationsQueryVariables) => variables === undefined ? ['Organizations'] : ['Organizations', variables]; +useOrganizationsQuery.getKey = (variables: OrganizationsQueryVariables) => ['Organizations', variables]; export const useInfiniteOrganizationsQuery = < TData = InfiniteData, @@ -3639,14 +3640,14 @@ export const useInfiniteOrganizationsQuery = < (() => { const { queryKey: optionsQueryKey, ...restOptions } = options; return { - queryKey: optionsQueryKey ?? variables === undefined ? ['Organizations.infinite'] : ['Organizations.infinite', variables], + queryKey: optionsQueryKey ?? ['Organizations.infinite', variables], queryFn: (metaData) => query({...variables, ...(metaData.pageParam ?? {})}), ...restOptions } })() )}; -useInfiniteOrganizationsQuery.getKey = (variables?: OrganizationsQueryVariables) => variables === undefined ? ['Organizations.infinite'] : ['Organizations.infinite', variables]; +useInfiniteOrganizationsQuery.getKey = (variables: OrganizationsQueryVariables) => ['Organizations.infinite', variables]; export const PostsDocument = ` query Posts($projectId: UUID!) { diff --git a/src/lib/graphql/queries/organizations.query.graphql b/src/lib/graphql/queries/organizations.query.graphql index 4b1d2ce11..55a50835c 100644 --- a/src/lib/graphql/queries/organizations.query.graphql +++ b/src/lib/graphql/queries/organizations.query.graphql @@ -1,12 +1,16 @@ query Organizations( $first: Int $orderBy: [OrganizationOrderBy!] + $userId: UUID! $search: String ) { organizations( first: $first orderBy: $orderBy - filter: { name: { includesInsensitive: $search } } + filter: { + name: { includesInsensitive: $search } + userOrganizations: { some: { userId: { equalTo: $userId } } } + } ) { nodes { rowId From 6dc92d9cbed503475c32f2cb9a15c37f5e3985ca Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 13:46:00 -0600 Subject: [PATCH 05/72] refactor(dashboard): remove average response time aggregate --- src/components/dashboard/DashboardPage/DashboardPage.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/dashboard/DashboardPage/DashboardPage.tsx b/src/components/dashboard/DashboardPage/DashboardPage.tsx index c50a5b2ac..a6346f5c6 100644 --- a/src/components/dashboard/DashboardPage/DashboardPage.tsx +++ b/src/components/dashboard/DashboardPage/DashboardPage.tsx @@ -1,7 +1,6 @@ "use client"; import { Grid } from "@omnidev/sigil"; -import { GoClock } from "react-icons/go"; import { HiOutlineChatBubbleLeftRight, HiOutlineUserGroup, @@ -45,11 +44,6 @@ const DashboardPage = () => { value: dashboardAggregates?.totalUsers ?? 0, icon: HiOutlineUserGroup, }, - { - title: app.dashboardPage.aggregates.avgResponseTime.title, - value: "4.20h", - icon: GoClock, - }, ]; return ( @@ -68,7 +62,7 @@ const DashboardPage = () => { > - + {aggregates.map(({ title, value, icon }) => ( Date: Sat, 28 Dec 2024 14:26:33 -0600 Subject: [PATCH 06/72] feature(dashboard): add query for recent feedback --- .../RecentFeedback/RecentFeedback.tsx | 32 ++++++--- .../dashboard/Response/Response.tsx | 13 ++-- src/generated/graphql.ts | 65 +++++++++++++++++++ .../queries/recentFeedback.query.graphql | 22 +++++++ 4 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 src/lib/graphql/queries/recentFeedback.query.graphql diff --git a/src/components/dashboard/RecentFeedback/RecentFeedback.tsx b/src/components/dashboard/RecentFeedback/RecentFeedback.tsx index 38e727230..880c545da 100644 --- a/src/components/dashboard/RecentFeedback/RecentFeedback.tsx +++ b/src/components/dashboard/RecentFeedback/RecentFeedback.tsx @@ -5,9 +5,10 @@ import { Flex } from "@omnidev/sigil"; import { SkeletonArray } from "components/core"; import { FeedbackCard, Response } from "components/dashboard"; import { ErrorBoundary } from "components/layout"; -import { useDataState } from "lib/hooks"; +import { useAuth } from "lib/hooks"; import type { ResponseType } from "components/dashboard"; +import { useRecentFeedbackQuery } from "generated/graphql"; interface Feedback { id: string; @@ -80,7 +81,21 @@ const FEEDBACK: Feedback[] = [ * Recent feedback section. */ const RecentFeedback = () => { - const { isLoading, isError } = useDataState({ timeout: 500 }); + const { user } = useAuth(); + + const { + data: recentFeedback, + isLoading, + isError, + } = useRecentFeedbackQuery( + { + userId: user?.id!, + }, + { + enabled: !!user, + select: (data) => data?.posts?.nodes, + } + ); return ( { {isLoading ? ( ) : ( - FEEDBACK.map(({ id, sender, message, date, type }) => ( + recentFeedback?.map((feedback) => ( )) )} diff --git a/src/components/dashboard/Response/Response.tsx b/src/components/dashboard/Response/Response.tsx index cbe35a83e..4df48afb8 100644 --- a/src/components/dashboard/Response/Response.tsx +++ b/src/components/dashboard/Response/Response.tsx @@ -1,6 +1,7 @@ "use client"; import { Badge, Flex, Text } from "@omnidev/sigil"; +import dayjs from "dayjs"; import { match } from "ts-pattern"; // NB: tried to use an enum here but had difficulties with runtime errors @@ -9,13 +10,13 @@ export type ResponseType = "Neutral" | "Positive" | "Bug" | "Feature"; // NB: this prop drilling is under the assumption that the query from parent won't provide much overhead (i.e. parent is isolated query and has minimal nesting / a response is a direct child) interface Props { /** Feedback sender. */ - sender: string; + sender: string | undefined; /** Feedback message. */ - message: string; + message: string | null | undefined; /** Date feedback was published. */ - date: string; + date: Date | null | undefined; /** Feedback type (i.e. category). */ - type: ResponseType; + type: ResponseType | undefined; } /** @@ -27,7 +28,7 @@ const Response = ({ sender, message, date, type }: Props) => { .with("Positive", () => "green") .with("Bug", () => "red") .with("Feature", () => "blue") - .exhaustive(); + .otherwise(() => "foreground.subtle"); return ( { - {date} + {dayjs(date).fromNow()} ); diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index 364bdc567..854aca98b 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -3333,6 +3333,13 @@ export type ProjectsQueryVariables = Exact<{ export type ProjectsQuery = { __typename?: 'Query', projects?: { __typename?: 'ProjectConnection', nodes: Array<{ __typename?: 'Project', rowId: string, name?: string | null, description?: string | null, slug?: string | null } | null> } | null }; +export type RecentFeedbackQueryVariables = Exact<{ + userId: Scalars['UUID']['input']; +}>; + + +export type RecentFeedbackQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', nodes: Array<{ __typename?: 'Post', rowId: string, createdAt?: Date | null, title?: string | null, description?: string | null, user?: { __typename?: 'User', rowId: string } | null } | null> } | null }; + export type UserQueryVariables = Exact<{ walletAddress: Scalars['String']['input']; }>; @@ -3816,6 +3823,64 @@ export const useInfiniteProjectsQuery = < useInfiniteProjectsQuery.getKey = (variables?: ProjectsQueryVariables) => variables === undefined ? ['Projects.infinite'] : ['Projects.infinite', variables]; +export const RecentFeedbackDocument = ` + query RecentFeedback($userId: UUID!) { + posts( + first: 5 + filter: {project: {organization: {userOrganizations: {some: {userId: {equalTo: $userId}}}}}} + ) { + nodes { + rowId + createdAt + title + description + user { + rowId + } + } + } +} + `; + +export const useRecentFeedbackQuery = < + TData = RecentFeedbackQuery, + TError = unknown + >( + variables: RecentFeedbackQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } + ) => { + + return useQuery( + { + queryKey: ['RecentFeedback', variables], + queryFn: useGraphqlClient(RecentFeedbackDocument).bind(null, variables), + ...options + } + )}; + +useRecentFeedbackQuery.getKey = (variables: RecentFeedbackQueryVariables) => ['RecentFeedback', variables]; + +export const useInfiniteRecentFeedbackQuery = < + TData = InfiniteData, + TError = unknown + >( + variables: RecentFeedbackQueryVariables, + options: Omit, 'queryKey'> & { queryKey?: UseInfiniteQueryOptions['queryKey'] } + ) => { + const query = useGraphqlClient(RecentFeedbackDocument) + return useInfiniteQuery( + (() => { + const { queryKey: optionsQueryKey, ...restOptions } = options; + return { + queryKey: optionsQueryKey ?? ['RecentFeedback.infinite', variables], + queryFn: (metaData) => query({...variables, ...(metaData.pageParam ?? {})}), + ...restOptions + } + })() + )}; + +useInfiniteRecentFeedbackQuery.getKey = (variables: RecentFeedbackQueryVariables) => ['RecentFeedback.infinite', variables]; + export const UserDocument = ` query User($walletAddress: String!) { userByWalletAddress(walletAddress: $walletAddress) { diff --git a/src/lib/graphql/queries/recentFeedback.query.graphql b/src/lib/graphql/queries/recentFeedback.query.graphql new file mode 100644 index 000000000..e28aabaa3 --- /dev/null +++ b/src/lib/graphql/queries/recentFeedback.query.graphql @@ -0,0 +1,22 @@ +query RecentFeedback($userId: UUID!) { + posts( + first: 5 + filter: { + project: { + organization: { + userOrganizations: { some: { userId: { equalTo: $userId } } } + } + } + } + ) { + nodes { + rowId + createdAt + title + description + user { + rowId + } + } + } +} From 75be978b1fbee8d0070bf93779967ad6ab1913cb Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:33:51 -0600 Subject: [PATCH 07/72] chore(graphql): add comment regarding user first/last in recent feedback query --- src/lib/graphql/queries/recentFeedback.query.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/graphql/queries/recentFeedback.query.graphql b/src/lib/graphql/queries/recentFeedback.query.graphql index e28aabaa3..edaf58a52 100644 --- a/src/lib/graphql/queries/recentFeedback.query.graphql +++ b/src/lib/graphql/queries/recentFeedback.query.graphql @@ -16,6 +16,7 @@ query RecentFeedback($userId: UUID!) { description user { rowId + # TODO: add `firstName` and `lastName` once the user is synced with the database } } } From dd2db616b68776076e1f3c7757cafc1ecd1273b0 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 15:09:33 -0600 Subject: [PATCH 08/72] feature(graphql): add query for weekly feedback aggregates --- src/generated/graphql.ts | 62 +++++++++++++++++++ .../queries/weeklyFeedback.query.graphql | 19 ++++++ 2 files changed, 81 insertions(+) create mode 100644 src/lib/graphql/queries/weeklyFeedback.query.graphql diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index 854aca98b..68ba2c1e1 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -3347,6 +3347,14 @@ export type UserQueryVariables = Exact<{ export type UserQuery = { __typename?: 'Query', userByWalletAddress?: { __typename?: 'User', createdAt?: Date | null, id: string, rowId: string, updatedAt?: Date | null, walletAddress?: string | null, userOrganizations: { __typename?: 'UserOrganizationConnection', nodes: Array<{ __typename?: 'UserOrganization', createdAt?: Date | null, organizationId: string, userId: string, organization?: { __typename?: 'Organization', id: string, createdAt?: Date | null, name?: string | null, rowId: string, slug?: string | null, updatedAt?: Date | null, projects: { __typename?: 'ProjectConnection', nodes: Array<{ __typename?: 'Project', slug?: string | null, rowId: string, organizationId: string, name?: string | null, image?: string | null, id: string, description?: string | null, createdAt?: Date | null } | null> } } | null } | null> } } | null }; +export type WeeklyFeedbackQueryVariables = Exact<{ + userId: Scalars['UUID']['input']; + startDate: Scalars['Datetime']['input']; +}>; + + +export type WeeklyFeedbackQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', groupedAggregates?: Array<{ __typename?: 'PostAggregates', keys?: Array | null, distinctCount?: { __typename?: 'PostDistinctCountAggregates', rowId?: any | null } | null }> | null } | null }; + export const ProjectFragmentDoc = ` fragment Project on Project { @@ -3927,3 +3935,57 @@ export const useInfiniteUserQuery = < )}; useInfiniteUserQuery.getKey = (variables: UserQueryVariables) => ['User.infinite', variables]; + +export const WeeklyFeedbackDocument = ` + query WeeklyFeedback($userId: UUID!, $startDate: Datetime!) { + posts( + filter: {project: {organization: {userOrganizations: {some: {userId: {equalTo: $userId}}}}}, createdAt: {greaterThanOrEqualTo: $startDate}} + ) { + groupedAggregates(groupBy: [CREATED_AT_TRUNCATED_TO_DAY]) { + keys + distinctCount { + rowId + } + } + } +} + `; + +export const useWeeklyFeedbackQuery = < + TData = WeeklyFeedbackQuery, + TError = unknown + >( + variables: WeeklyFeedbackQueryVariables, + options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } + ) => { + + return useQuery( + { + queryKey: ['WeeklyFeedback', variables], + queryFn: useGraphqlClient(WeeklyFeedbackDocument).bind(null, variables), + ...options + } + )}; + +useWeeklyFeedbackQuery.getKey = (variables: WeeklyFeedbackQueryVariables) => ['WeeklyFeedback', variables]; + +export const useInfiniteWeeklyFeedbackQuery = < + TData = InfiniteData, + TError = unknown + >( + variables: WeeklyFeedbackQueryVariables, + options: Omit, 'queryKey'> & { queryKey?: UseInfiniteQueryOptions['queryKey'] } + ) => { + const query = useGraphqlClient(WeeklyFeedbackDocument) + return useInfiniteQuery( + (() => { + const { queryKey: optionsQueryKey, ...restOptions } = options; + return { + queryKey: optionsQueryKey ?? ['WeeklyFeedback.infinite', variables], + queryFn: (metaData) => query({...variables, ...(metaData.pageParam ?? {})}), + ...restOptions + } + })() + )}; + +useInfiniteWeeklyFeedbackQuery.getKey = (variables: WeeklyFeedbackQueryVariables) => ['WeeklyFeedback.infinite', variables]; diff --git a/src/lib/graphql/queries/weeklyFeedback.query.graphql b/src/lib/graphql/queries/weeklyFeedback.query.graphql new file mode 100644 index 000000000..476ed6386 --- /dev/null +++ b/src/lib/graphql/queries/weeklyFeedback.query.graphql @@ -0,0 +1,19 @@ +query WeeklyFeedback($userId: UUID!, $startDate: Datetime!) { + posts( + filter: { + project: { + organization: { + userOrganizations: { some: { userId: { equalTo: $userId } } } + } + } + createdAt: { greaterThanOrEqualTo: $startDate } + } + ) { + groupedAggregates(groupBy: [CREATED_AT_TRUNCATED_TO_DAY]) { + keys + distinctCount { + rowId + } + } + } +} From f55bdc0e172e61d5229c5542ce6f7e8cdc483f73 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 16:29:29 -0600 Subject: [PATCH 09/72] feature(dashboard): apply query for generating feedback overview chart --- .../FeedbackOverview/FeedbackOverview.tsx | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx index 629daf5fc..ca376ea76 100644 --- a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx +++ b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx @@ -1,6 +1,7 @@ "use client"; import { Skeleton, useIsClient } from "@omnidev/sigil"; +import dayjs from "dayjs"; import { Bar, BarChart, @@ -12,26 +13,53 @@ import { import { FeedbackCard, FeedbackTooltip } from "components/dashboard"; import { ErrorBoundary } from "components/layout"; +import { useWeeklyFeedbackQuery } from "generated/graphql"; import { token } from "generated/panda/tokens"; -import { useDataState } from "lib/hooks"; - -const getRandomInteger = () => Math.floor(Math.random() * 100); +import { useAuth } from "lib/hooks"; /** * Feedback overview section. Displays a bar chart that displays daily feedback volume for the past 7 days. */ const FeedbackOverview = () => { - const isClient = useIsClient(), - { isLoading, isError } = useDataState({ timeout: 600 }); + const isClient = useIsClient(); + + const { user } = useAuth(); + + const oneWeekAgo = dayjs().subtract(1, "week").startOf("day").toDate(); + + const getFormattedDate = (diff: number) => + dayjs(oneWeekAgo).add(diff, "day").format("ddd"); + + const { + data: weeklyFeedback, + isLoading, + isError, + } = useWeeklyFeedbackQuery( + { + userId: user?.id!, + startDate: oneWeekAgo, + }, + { + enabled: !!user, + select: (data) => + data?.posts?.groupedAggregates?.map((aggregate) => ({ + name: dayjs(aggregate.keys?.[0]).format("ddd"), + total: Number(aggregate.distinctCount?.rowId), + })), + } + ); + + const getDailyTotal = (date: string) => + weeklyFeedback?.find((item) => item.name === date)?.total ?? 0; const DATA = [ - { name: "Mon", total: getRandomInteger() }, - { name: "Tue", total: getRandomInteger() }, - { name: "Wed", total: getRandomInteger() }, - { name: "Thu", total: getRandomInteger() }, - { name: "Fri", total: getRandomInteger() }, - { name: "Sat", total: getRandomInteger() }, - { name: "Sun", total: getRandomInteger() }, + { name: getFormattedDate(0), total: getDailyTotal(getFormattedDate(0)) }, + { name: getFormattedDate(1), total: getDailyTotal(getFormattedDate(1)) }, + { name: getFormattedDate(2), total: getDailyTotal(getFormattedDate(2)) }, + { name: getFormattedDate(3), total: getDailyTotal(getFormattedDate(3)) }, + { name: getFormattedDate(4), total: getDailyTotal(getFormattedDate(4)) }, + { name: getFormattedDate(5), total: getDailyTotal(getFormattedDate(5)) }, + { name: getFormattedDate(6), total: getDailyTotal(getFormattedDate(6)) }, ]; return ( From b741fe1a02ffbaed701b885bde719e9a3f2851f4 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 16:46:52 -0600 Subject: [PATCH 10/72] refactor(dashboard): remove date formatting functions from react context --- .../dashboard/FeedbackOverview/FeedbackOverview.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx index ca376ea76..fb40c86b0 100644 --- a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx +++ b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx @@ -17,6 +17,11 @@ import { useWeeklyFeedbackQuery } from "generated/graphql"; import { token } from "generated/panda/tokens"; import { useAuth } from "lib/hooks"; +const oneWeekAgo = dayjs().subtract(1, "week").startOf("day").toDate(); + + const getFormattedDate = (diff: number) => + dayjs(oneWeekAgo).add(diff, "day").format("ddd"); + /** * Feedback overview section. Displays a bar chart that displays daily feedback volume for the past 7 days. */ @@ -25,11 +30,6 @@ const FeedbackOverview = () => { const { user } = useAuth(); - const oneWeekAgo = dayjs().subtract(1, "week").startOf("day").toDate(); - - const getFormattedDate = (diff: number) => - dayjs(oneWeekAgo).add(diff, "day").format("ddd"); - const { data: weeklyFeedback, isLoading, From 4d53b3e69207d76db483aefa37a335d81a4a93af Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 16:47:33 -0600 Subject: [PATCH 11/72] chore: format --- .../dashboard/FeedbackOverview/FeedbackOverview.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx index fb40c86b0..e3e774753 100644 --- a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx +++ b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx @@ -19,9 +19,9 @@ import { useAuth } from "lib/hooks"; const oneWeekAgo = dayjs().subtract(1, "week").startOf("day").toDate(); - const getFormattedDate = (diff: number) => - dayjs(oneWeekAgo).add(diff, "day").format("ddd"); - +const getFormattedDate = (diff: number) => + dayjs(oneWeekAgo).add(diff, "day").format("ddd"); + /** * Feedback overview section. Displays a bar chart that displays daily feedback volume for the past 7 days. */ From 1fac34e53220bcd71d6f3d72c8ac10a2b7558a49 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 16:50:21 -0600 Subject: [PATCH 12/72] refactor(dashboard): temporarily disabled new project button --- src/components/dashboard/DashboardPage/DashboardPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/dashboard/DashboardPage/DashboardPage.tsx b/src/components/dashboard/DashboardPage/DashboardPage.tsx index a6346f5c6..24bcd0e16 100644 --- a/src/components/dashboard/DashboardPage/DashboardPage.tsx +++ b/src/components/dashboard/DashboardPage/DashboardPage.tsx @@ -56,6 +56,7 @@ const DashboardPage = () => { label: app.dashboardPage.cta.newProject.label, // TODO: get Sigil Icon component working and update accordingly. Context: https://github.com/omnidotdev/backfeed-app/pull/44#discussion_r1897974331 icon: , + disabled: true, }, ], }} From 113f9aa214cf7ca1f349f0db6d093e9affade7f9 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 16:57:08 -0600 Subject: [PATCH 13/72] refactor(codegen): update scalars to map BigInt to string --- src/generated/graphql.ts | 6 +++--- src/lib/graphql/codegen.config.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index 68ba2c1e1..f7fc92f4b 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -15,7 +15,7 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } - BigInt: { input: any; output: any; } + BigInt: { input: string; output: string; } Cursor: { input: string; output: string; } Datetime: { input: Date; output: Date; } UUID: { input: string; output: string; } @@ -3316,7 +3316,7 @@ export type PostsQueryVariables = Exact<{ }>; -export type PostsQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', nodes: Array<{ __typename?: 'Post', rowId: string, createdAt?: Date | null, title?: string | null, description?: string | null, user?: { __typename?: 'User', walletAddress?: string | null } | null, upvotes: { __typename?: 'UpvoteConnection', aggregates?: { __typename?: 'UpvoteAggregates', distinctCount?: { __typename?: 'UpvoteDistinctCountAggregates', rowId?: any | null } | null } | null, nodes: Array<{ __typename?: 'Upvote', rowId: string } | null> } } | null> } | null }; +export type PostsQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', nodes: Array<{ __typename?: 'Post', rowId: string, createdAt?: Date | null, title?: string | null, description?: string | null, user?: { __typename?: 'User', walletAddress?: string | null } | null, upvotes: { __typename?: 'UpvoteConnection', aggregates?: { __typename?: 'UpvoteAggregates', distinctCount?: { __typename?: 'UpvoteDistinctCountAggregates', rowId?: string | null } | null } | null, nodes: Array<{ __typename?: 'Upvote', rowId: string } | null> } } | null> } | null }; export type ProjectQueryVariables = Exact<{ organizationId: Scalars['UUID']['input']; @@ -3353,7 +3353,7 @@ export type WeeklyFeedbackQueryVariables = Exact<{ }>; -export type WeeklyFeedbackQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', groupedAggregates?: Array<{ __typename?: 'PostAggregates', keys?: Array | null, distinctCount?: { __typename?: 'PostDistinctCountAggregates', rowId?: any | null } | null }> | null } | null }; +export type WeeklyFeedbackQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', groupedAggregates?: Array<{ __typename?: 'PostAggregates', keys?: Array | null, distinctCount?: { __typename?: 'PostDistinctCountAggregates', rowId?: string | null } | null }> | null } | null }; export const ProjectFragmentDoc = ` diff --git a/src/lib/graphql/codegen.config.ts b/src/lib/graphql/codegen.config.ts index 2421ba747..25b30fac2 100644 --- a/src/lib/graphql/codegen.config.ts +++ b/src/lib/graphql/codegen.config.ts @@ -39,6 +39,7 @@ const graphqlCodegenConfig: CodegenConfig = { Datetime: "Date", UUID: "string", Cursor: "string", + BigInt: "string", }, // https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-react-query#using-graphql-request // fetcher: "graphql-request", From 0d18428fa51bd65bbb99eaa47b16a11857874464 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 18:53:20 -0600 Subject: [PATCH 14/72] refactor(dashboard): simplify logic for feedback chart data --- .../dashboard/FeedbackOverview/FeedbackOverview.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx index e3e774753..1ed0d1d0f 100644 --- a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx +++ b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx @@ -52,15 +52,10 @@ const FeedbackOverview = () => { const getDailyTotal = (date: string) => weeklyFeedback?.find((item) => item.name === date)?.total ?? 0; - const DATA = [ - { name: getFormattedDate(0), total: getDailyTotal(getFormattedDate(0)) }, - { name: getFormattedDate(1), total: getDailyTotal(getFormattedDate(1)) }, - { name: getFormattedDate(2), total: getDailyTotal(getFormattedDate(2)) }, - { name: getFormattedDate(3), total: getDailyTotal(getFormattedDate(3)) }, - { name: getFormattedDate(4), total: getDailyTotal(getFormattedDate(4)) }, - { name: getFormattedDate(5), total: getDailyTotal(getFormattedDate(5)) }, - { name: getFormattedDate(6), total: getDailyTotal(getFormattedDate(6)) }, - ]; + const DATA = Array.from({ length: 7 }).map((_, index) => ({ + name: getFormattedDate(index), + total: getDailyTotal(getFormattedDate(index)), + })); return ( Date: Sat, 28 Dec 2024 18:56:48 -0600 Subject: [PATCH 15/72] refactor(dashboard): dedup formatted date from data array in feedback overview --- .../dashboard/FeedbackOverview/FeedbackOverview.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx index 1ed0d1d0f..4dfdda178 100644 --- a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx +++ b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx @@ -52,10 +52,14 @@ const FeedbackOverview = () => { const getDailyTotal = (date: string) => weeklyFeedback?.find((item) => item.name === date)?.total ?? 0; - const DATA = Array.from({ length: 7 }).map((_, index) => ({ - name: getFormattedDate(index), - total: getDailyTotal(getFormattedDate(index)), - })); + const DATA = Array.from({ length: 7 }).map((_, index) => { + const date = getFormattedDate(index); + + return { + name: date, + total: getDailyTotal(date), + }; + }); return ( Date: Sat, 28 Dec 2024 18:57:23 -0600 Subject: [PATCH 16/72] chore: remove newline --- src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx index 4dfdda178..92556e6b2 100644 --- a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx +++ b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx @@ -54,7 +54,7 @@ const FeedbackOverview = () => { const DATA = Array.from({ length: 7 }).map((_, index) => { const date = getFormattedDate(index); - + return { name: date, total: getDailyTotal(date), From 9c9bdf0b81ac4f6a1c8dd566137064eec0fdcf28 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 19:13:33 -0600 Subject: [PATCH 17/72] refactor(recent-feedback): update props for response --- .../RecentFeedback/RecentFeedback.tsx | 75 +------------------ .../dashboard/Response/Response.tsx | 22 +++--- 2 files changed, 13 insertions(+), 84 deletions(-) diff --git a/src/components/dashboard/RecentFeedback/RecentFeedback.tsx b/src/components/dashboard/RecentFeedback/RecentFeedback.tsx index 880c545da..99af90f30 100644 --- a/src/components/dashboard/RecentFeedback/RecentFeedback.tsx +++ b/src/components/dashboard/RecentFeedback/RecentFeedback.tsx @@ -5,77 +5,10 @@ import { Flex } from "@omnidev/sigil"; import { SkeletonArray } from "components/core"; import { FeedbackCard, Response } from "components/dashboard"; import { ErrorBoundary } from "components/layout"; -import { useAuth } from "lib/hooks"; - -import type { ResponseType } from "components/dashboard"; import { useRecentFeedbackQuery } from "generated/graphql"; +import { useAuth } from "lib/hooks"; -interface Feedback { - id: string; - sender: string; - message: string; - date: string; - type: ResponseType; -} - -const FEEDBACK: Feedback[] = [ - { - id: "1", - sender: "Back Feed", - message: "I still like turtles.", - date: "30 seconds ago", - type: "Neutral", - }, - { - id: "2", - sender: "Feed Back", - message: "The new dashboard layout is much more intuitive!", - date: "4 minutes ago", - type: "Positive", - }, - { - id: "3", - sender: "Fed Front", - message: "Having issues with the new export feature.", - date: "3 hours ago", - type: "Bug", - }, - { - id: "4", - sender: "Back Fed", - message: "Would love to be able to export feedback.", - date: "2 days ago", - type: "Feature", - }, - { - id: "5", - sender: "Back Feed", - message: "I am having troubles logging in.", - date: "3 days ago", - type: "Bug", - }, - { - id: "6", - sender: "Back Fed", - message: "I love turtles!", - date: "4 days ago", - type: "Positive", - }, - { - id: "7", - sender: "Front Fed", - message: "Would love to be able to export feedback.", - date: "10 days ago", - type: "Feature", - }, - { - id: "8", - sender: "Back Feed", - message: "I like turtles.", - date: "69 days ago", - type: "Neutral", - }, -]; +import type { Post } from "generated/graphql"; /** * Recent feedback section. @@ -117,9 +50,7 @@ const RecentFeedback = () => { recentFeedback?.map((feedback) => ( } // TODO: make this dynamic once the data is streamed in type="Neutral" /> diff --git a/src/components/dashboard/Response/Response.tsx b/src/components/dashboard/Response/Response.tsx index 4df48afb8..5efc376d1 100644 --- a/src/components/dashboard/Response/Response.tsx +++ b/src/components/dashboard/Response/Response.tsx @@ -4,25 +4,23 @@ import { Badge, Flex, Text } from "@omnidev/sigil"; import dayjs from "dayjs"; import { match } from "ts-pattern"; +import type { Post } from "generated/graphql"; + // NB: tried to use an enum here but had difficulties with runtime errors export type ResponseType = "Neutral" | "Positive" | "Bug" | "Feature"; // NB: this prop drilling is under the assumption that the query from parent won't provide much overhead (i.e. parent is isolated query and has minimal nesting / a response is a direct child) interface Props { - /** Feedback sender. */ - sender: string | undefined; - /** Feedback message. */ - message: string | null | undefined; - /** Date feedback was published. */ - date: Date | null | undefined; - /** Feedback type (i.e. category). */ - type: ResponseType | undefined; + /** Feedback details. */ + feedback: Partial; + /** Feedback type */ + type: ResponseType; } /** * Recent feedback response. */ -const Response = ({ sender, message, date, type }: Props) => { +const Response = ({ feedback, type }: Props) => { const color = match(type) .with("Neutral", () => "foreground.subtle") .with("Positive", () => "green") @@ -41,7 +39,7 @@ const Response = ({ sender, message, date, type }: Props) => { - {sender} + {feedback?.rowId} @@ -50,12 +48,12 @@ const Response = ({ sender, message, date, type }: Props) => { - {message} + {feedback?.description} - {dayjs(date).fromNow()} + {dayjs(feedback?.createdAt).fromNow()} ); From 0ca4c5988c82555ed27ec3017de5568444014faa Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 19:15:58 -0600 Subject: [PATCH 18/72] chore: update comments --- src/components/dashboard/RecentFeedback/RecentFeedback.tsx | 1 - src/components/dashboard/Response/Response.tsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dashboard/RecentFeedback/RecentFeedback.tsx b/src/components/dashboard/RecentFeedback/RecentFeedback.tsx index 99af90f30..4e5980eef 100644 --- a/src/components/dashboard/RecentFeedback/RecentFeedback.tsx +++ b/src/components/dashboard/RecentFeedback/RecentFeedback.tsx @@ -51,7 +51,6 @@ const RecentFeedback = () => { } - // TODO: make this dynamic once the data is streamed in type="Neutral" /> )) diff --git a/src/components/dashboard/Response/Response.tsx b/src/components/dashboard/Response/Response.tsx index 5efc376d1..a23cd8fa1 100644 --- a/src/components/dashboard/Response/Response.tsx +++ b/src/components/dashboard/Response/Response.tsx @@ -14,6 +14,7 @@ interface Props { /** Feedback details. */ feedback: Partial; /** Feedback type */ + // TODO: remove and capture from `feedback` prop once discussed / db schema is updated type: ResponseType; } From 6d6c97685bc827e55b92668ced4bfd92f1d72d59 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 22:15:22 -0600 Subject: [PATCH 19/72] docs: update response jsdoc Co-authored-by: Brian Cooper <20056195+coopbri@users.noreply.github.com> --- src/components/dashboard/Response/Response.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dashboard/Response/Response.tsx b/src/components/dashboard/Response/Response.tsx index a23cd8fa1..65c2fd23b 100644 --- a/src/components/dashboard/Response/Response.tsx +++ b/src/components/dashboard/Response/Response.tsx @@ -13,7 +13,7 @@ export type ResponseType = "Neutral" | "Positive" | "Bug" | "Feature"; interface Props { /** Feedback details. */ feedback: Partial; - /** Feedback type */ + /** Feedback type. */ // TODO: remove and capture from `feedback` prop once discussed / db schema is updated type: ResponseType; } From fc15b13ca919c26ed568e581c390618422159aed Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sat, 28 Dec 2024 22:30:27 -0600 Subject: [PATCH 20/72] refactor(feedback-overview): set yaxis to always scale on natural numbers --- .../dashboard/FeedbackOverview/FeedbackOverview.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx index 92556e6b2..f0d4c4191 100644 --- a/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx +++ b/src/components/dashboard/FeedbackOverview/FeedbackOverview.tsx @@ -79,7 +79,12 @@ const FeedbackOverview = () => { {/* NB: the explicit width removes some unecessary spacing on the y-axis. This should be fine for 3-digit numbers, but may need to be adjusted for larger numbers. */} - + Date: Sun, 29 Dec 2024 10:37:27 -0600 Subject: [PATCH 21/72] refactor(graphql): update organization query --- src/generated/graphql.ts | 21 ++++++++++++++----- .../queries/organization.query.graphql | 17 ++++++++++++--- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index f7fc92f4b..38ecfe5da 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -3295,11 +3295,11 @@ export type DashboardAggregatesQueryVariables = Exact<{ export type DashboardAggregatesQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', totalCount: number } | null, users?: { __typename?: 'UserConnection', totalCount: number } | null }; export type OrganizationQueryVariables = Exact<{ - slug: Scalars['String']['input']; + rowId: Scalars['UUID']['input']; }>; -export type OrganizationQuery = { __typename?: 'Query', organizationBySlug?: { __typename?: 'Organization', rowId: string, name?: string | null, slug?: string | null } | null }; +export type OrganizationQuery = { __typename?: 'Query', organization?: { __typename?: 'Organization', rowId: string, name?: string | null, userOrganizations: { __typename?: 'UserOrganizationConnection', totalCount: number }, projects: { __typename?: 'ProjectConnection', nodes: Array<{ __typename?: 'Project', name?: string | null, description?: string | null, posts: { __typename?: 'PostConnection', totalCount: number } } | null> } } | null }; export type OrganizationsQueryVariables = Exact<{ first?: InputMaybe; @@ -3556,11 +3556,22 @@ export const useInfiniteDashboardAggregatesQuery = < useInfiniteDashboardAggregatesQuery.getKey = (variables: DashboardAggregatesQueryVariables) => ['DashboardAggregates.infinite', variables]; export const OrganizationDocument = ` - query Organization($slug: String!) { - organizationBySlug(slug: $slug) { + query Organization($rowId: UUID!) { + organization(rowId: $rowId) { rowId name - slug + userOrganizations { + totalCount + } + projects { + nodes { + name + description + posts { + totalCount + } + } + } } } `; diff --git a/src/lib/graphql/queries/organization.query.graphql b/src/lib/graphql/queries/organization.query.graphql index 8cffe3200..0b944d089 100644 --- a/src/lib/graphql/queries/organization.query.graphql +++ b/src/lib/graphql/queries/organization.query.graphql @@ -1,7 +1,18 @@ -query Organization($slug: String!) { - organizationBySlug(slug: $slug) { +query Organization($rowId: UUID!) { + organization(rowId: $rowId) { rowId name - slug + userOrganizations { + totalCount + } + projects { + nodes { + name + description + posts { + totalCount + } + } + } } } From 5cb27bdb4973b444b54a2e8a664f41c766c82e1d Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sun, 29 Dec 2024 10:54:16 -0600 Subject: [PATCH 22/72] refactor(organization): cherry-pick partial support for RSC --- .../organizations/[organizationId]/page.tsx | 53 ++++++------------- src/components/organization/index.ts | 1 + 2 files changed, 17 insertions(+), 37 deletions(-) diff --git a/src/app/organizations/[organizationId]/page.tsx b/src/app/organizations/[organizationId]/page.tsx index fe95e69bc..41671fe7a 100644 --- a/src/app/organizations/[organizationId]/page.tsx +++ b/src/app/organizations/[organizationId]/page.tsx @@ -1,40 +1,32 @@ -"use client"; - -import { Grid } from "@omnidev/sigil"; -import { notFound, useParams, useRouter } from "next/navigation"; +import { notFound } from "next/navigation"; import { HiOutlineFolder } from "react-icons/hi2"; import { LuPlusCircle } from "react-icons/lu"; import { Page } from "components/layout"; -import { - OrganizationActions, - OrganizationMetrics, - OrganizationProjectsOverview, -} from "components/organization"; +import { OrganizationOverview } from "components/organization"; import { app } from "lib/config"; -import { useAuth, useDataState } from "lib/hooks"; +import { getAuthSession } from "lib/util"; + +interface Props { + /** Organization page params. */ + params: Promise<{ organizationId: string }>; +} /** * Organization overview page. */ -const OrganizationPage = () => { - const { isAuthenticated } = useAuth(); - - const params = useParams<{ organizationId: string }>(), - router = useRouter(); - - const { isLoading, isError } = useDataState(); - - const navigateToProjectsPage = () => - router.push(`/organizations/${params.organizationId}/projects`); +const OrganizationPage = async ({ params }: Props) => { + const { organizationId } = await params; + + const session = await getAuthSession(); // TODO: when data is streamed in, this condition should be updated to check for the existence of the organization - if (!isAuthenticated) notFound(); + if (!session) notFound(); return ( { // TODO: get Sigil Icon component working and update accordingly. Context: https://github.com/omnidotdev/backfeed-app/pull/44#discussion_r1897974331 icon: , variant: "outline", - onClick: navigateToProjectsPage, + // TODO: add href upon merge of https://github.com/omnidotdev/backfeed-app/pull/39 }, { label: app.organizationPage.header.cta.newProject.label, @@ -52,20 +44,7 @@ const OrganizationPage = () => { ], }} > - - - - {/* NB: these aggregates should be fine to fetch from the top level `organizationQuery` */} - - - - + ); }; diff --git a/src/components/organization/index.ts b/src/components/organization/index.ts index f5b9a89fd..9ac659bce 100644 --- a/src/components/organization/index.ts +++ b/src/components/organization/index.ts @@ -3,5 +3,6 @@ export { default as OrganizationFilters } from "./OrganizationFilters/Organizati export { default as OrganizationList } from "./OrganizationList/OrganizationList"; export { default as OrganizationListItem } from "./OrganizationListItem/OrganizationListItem"; export { default as OrganizationMetrics } from "./OrganizationMetrics/OrganizationMetrics"; +export { default as OrganizationOverview } from "./OrganizationOverview/OrganizationOverview"; export { default as OrganizationProjectsOverview } from "./OrganizationProjectsOverview/OrganizationProjectsOverview"; export { default as ProjectCard } from "./ProjectCard/ProjectCard"; From 1fe3ac0b7158d22814a460acc4d48276b1700412 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sun, 29 Dec 2024 10:55:09 -0600 Subject: [PATCH 23/72] refactor(organization): cherry-pick partial support for RSC --- .../organizations/[organizationId]/page.tsx | 2 +- .../OrganizationOverview.tsx | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/components/organization/OrganizationOverview/OrganizationOverview.tsx diff --git a/src/app/organizations/[organizationId]/page.tsx b/src/app/organizations/[organizationId]/page.tsx index 41671fe7a..0ce9facd2 100644 --- a/src/app/organizations/[organizationId]/page.tsx +++ b/src/app/organizations/[organizationId]/page.tsx @@ -17,7 +17,7 @@ interface Props { */ const OrganizationPage = async ({ params }: Props) => { const { organizationId } = await params; - + const session = await getAuthSession(); // TODO: when data is streamed in, this condition should be updated to check for the existence of the organization diff --git a/src/components/organization/OrganizationOverview/OrganizationOverview.tsx b/src/components/organization/OrganizationOverview/OrganizationOverview.tsx new file mode 100644 index 000000000..d0fc823ed --- /dev/null +++ b/src/components/organization/OrganizationOverview/OrganizationOverview.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { Grid } from "@omnidev/sigil"; +import { + OrganizationActions, + OrganizationMetrics, + OrganizationProjectsOverview, +} from "components/organization"; +import { useDataState } from "lib/hooks"; + +const OrganizationOverview = () => { + const { isLoading, isError } = useDataState(); + + return ( + <> + + + + {/* NB: these aggregates should be fine to fetch from the top level `organizationQuery` */} + + + + + + ); +}; + +export default OrganizationOverview; From aaf75f8ff3fb518965ef01bb6339934ba94a20c6 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Sun, 29 Dec 2024 11:28:08 -0600 Subject: [PATCH 24/72] feature(organization): appropriately fetch information for projects --- .../organizations/[organizationId]/page.tsx | 19 ++++-- .../OrganizationOverview.tsx | 9 ++- .../OrganizationProjectsOverview.tsx | 60 ++++++------------- .../organization/ProjectCard/ProjectCard.tsx | 24 ++++---- src/generated/graphql.ts | 6 +- .../queries/organization.query.graphql | 4 +- 6 files changed, 53 insertions(+), 69 deletions(-) diff --git a/src/app/organizations/[organizationId]/page.tsx b/src/app/organizations/[organizationId]/page.tsx index 0ce9facd2..987e31723 100644 --- a/src/app/organizations/[organizationId]/page.tsx +++ b/src/app/organizations/[organizationId]/page.tsx @@ -1,12 +1,16 @@ +import request from "graphql-request"; import { notFound } from "next/navigation"; import { HiOutlineFolder } from "react-icons/hi2"; import { LuPlusCircle } from "react-icons/lu"; import { Page } from "components/layout"; import { OrganizationOverview } from "components/organization"; -import { app } from "lib/config"; +import { OrganizationDocument } from "generated/graphql"; +import { API_BASE_URL, app } from "lib/config"; import { getAuthSession } from "lib/util"; +import type { OrganizationQuery, OrganizationQueryVariables } from "generated/graphql"; + interface Props { /** Organization page params. */ params: Promise<{ organizationId: string }>; @@ -20,13 +24,18 @@ const OrganizationPage = async ({ params }: Props) => { const session = await getAuthSession(); - // TODO: when data is streamed in, this condition should be updated to check for the existence of the organization - if (!session) notFound(); + const { organization }: OrganizationQuery = await request({ + url: API_BASE_URL!, + document: OrganizationDocument, + variables: { rowId: organizationId } as OrganizationQueryVariables, + }) + + if (!session || !organization) notFound(); return ( { ], }} > - + ); }; diff --git a/src/components/organization/OrganizationOverview/OrganizationOverview.tsx b/src/components/organization/OrganizationOverview/OrganizationOverview.tsx index d0fc823ed..0a19f4fcf 100644 --- a/src/components/organization/OrganizationOverview/OrganizationOverview.tsx +++ b/src/components/organization/OrganizationOverview/OrganizationOverview.tsx @@ -8,12 +8,17 @@ import { } from "components/organization"; import { useDataState } from "lib/hooks"; -const OrganizationOverview = () => { +interface Props { + /** Organization ID. */ + organizationId: string; +} + +const OrganizationOverview = ({ organizationId }: Props) => { const { isLoading, isError } = useDataState(); return ( <> - + {/* NB: these aggregates should be fine to fetch from the top level `organizationQuery` */} diff --git a/src/components/organization/OrganizationProjectsOverview/OrganizationProjectsOverview.tsx b/src/components/organization/OrganizationProjectsOverview/OrganizationProjectsOverview.tsx index 6d544e530..f5d874b56 100644 --- a/src/components/organization/OrganizationProjectsOverview/OrganizationProjectsOverview.tsx +++ b/src/components/organization/OrganizationProjectsOverview/OrganizationProjectsOverview.tsx @@ -7,7 +7,9 @@ import { SkeletonArray } from "components/core"; import { ErrorBoundary, SectionContainer } from "components/layout"; import { ProjectCard } from "components/organization"; import { app } from "lib/config"; -import { useDataState } from "lib/hooks"; +import { useOrganizationQuery } from "generated/graphql"; + +import type { Project } from "generated/graphql"; interface OrganizationProject { /** Organization ID. */ @@ -18,47 +20,21 @@ interface OrganizationProject { description: string; } -const PROJECTS: OrganizationProject[] = [ - { - id: "1", - name: "Mobile App Feedback", - description: - "We are actively gathering detailed user feedback for our iOS and Android applications to enhance user experience and functionality. This includes identifying key pain points, usability issues, and feature requests from our diverse user base. Our primary focus is on improving app performance, refining navigation flows, and introducing user-driven features that align with customer needs. Additionally, we are seeking feedback on visual design updates and accessibility improvements to ensure the app meets the highest standards for all users. This project is crucial for maintaining our competitive edge in the mobile app market and fostering customer loyalty.", - }, - { - id: "2", - name: "Web Platform Beta", - description: "Beta testing feedback for the new web platform", - }, - { - id: "3", - name: "Desktop Client", - description: "User experience feedback for desktop applications", - }, - { - id: "4", - name: "E-commerce Platform Upgrade", - description: - "Feedback for the upgraded e-commerce platform features and user flow.", - }, - { - id: "5", - name: "AI Chatbot Testing", - description: "Testing and collecting responses for our AI chatbot.", - }, - { - id: "6", - name: "Enterprise CRM Feedback", - description: "Gathering feedback on our enterprise CRM system.", - }, -]; +interface Props { + /** Organization ID. */ + organizationId: string +} /** * Organization projects overview. */ -const OrganizationProjectsOverview = () => { - const { isLoading, isError } = useDataState(); - +const OrganizationProjectsOverview = ({ organizationId }: Props) => { + const { data: projects, isLoading, isError } = useOrganizationQuery({ + rowId: organizationId, + }, { + select: (data) => data?.organization?.projects?.nodes + }) + return ( { {isLoading ? ( ) : ( - PROJECTS.map(({ id, name, description }) => ( + projects?.map((project) => ( } // !!NB: explicitly set the height of the card to prevent CLS issues with loading and error states. h={48} /> diff --git a/src/components/organization/ProjectCard/ProjectCard.tsx b/src/components/organization/ProjectCard/ProjectCard.tsx index a7d75689a..bbb88e08a 100644 --- a/src/components/organization/ProjectCard/ProjectCard.tsx +++ b/src/components/organization/ProjectCard/ProjectCard.tsx @@ -21,13 +21,14 @@ import { OverflowText } from "components/core"; import { useDataState } from "lib/hooks"; import type { FlexProps } from "@omnidev/sigil"; +import type { Project } from "generated/graphql"; import type { IconType } from "react-icons"; interface ProjectMetric { /** Visual icon. */ icon: IconType; /** Metric value. */ - value: string | number; + value: number | undefined; /** Metric type. */ type: "Responses" | "Users" | "Updated"; /** Container props for `type` and `value`. Used to override default styles. */ @@ -35,18 +36,14 @@ interface ProjectMetric { } interface Props extends FlexProps { - /** Project ID. */ - id: string; - /** Name of the organization. */ - name: string; - /** Description of the organization. */ - description: string; + /** Project details. */ + project: Partial; } /** * Project, nested within an organization. A project outlines an application or other kind of product or service that aggregates and contains scoped feedback. */ -const ProjectCard = ({ id, name, description, ...rest }: Props) => { +const ProjectCard = ({ project, ...rest }: Props) => { // !NB: this is to represent where we would want to fetch the aggregate data (total feedback and active users). This will keep the top level `projectsQuery` clean. const { isLoading, isError } = useDataState({ timeout: 800 }); @@ -55,11 +52,12 @@ const ProjectCard = ({ id, name, description, ...rest }: Props) => { const PROJECT_METRICS: ProjectMetric[] = [ { icon: HiOutlineChatBubbleLeftRight, - value: 420, + value: project?.posts?.totalCount, type: "Responses", }, { icon: HiOutlineUserGroup, + // TODO: determine the best way to get the totally number of users for a project value: 69, type: "Users", }, @@ -75,7 +73,9 @@ const ProjectCard = ({ id, name, description, ...rest }: Props) => { p={8} {...rest} > - + + )} + +); + +export default EmptyState; diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts index fb0656e6f..61c5c1181 100644 --- a/src/components/layout/index.ts +++ b/src/components/layout/index.ts @@ -1,4 +1,5 @@ export { default as AccountInformation } from "./AccountInformation/AccountInformation"; +export { default as EmptyState } from "./EmptyState/EmptyState"; export { default as ErrorBoundary } from "./ErrorBoundary/ErrorBoundary"; export { default as Footer } from "./Footer/Footer"; export { default as Header } from "./Header/Header"; diff --git a/src/lib/config/app.config.ts b/src/lib/config/app.config.ts index 6e5c6ae7e..dee6e7ada 100644 --- a/src/lib/config/app.config.ts +++ b/src/lib/config/app.config.ts @@ -87,6 +87,17 @@ const app = { organizations: { title: "Organizations", description: "Manage your organizations and their feedback projects", + emptyState: { + message: "No organizations found. Would you like to create one?", + cta: { + label: "Create Organization", + }, + }, + }, + recentFeedback: { + emptyState: { + message: "No recent feedback found.", + }, }, aggregates: { totalFeedback: { From fe6a7e6f8001f6593e7d69a579e98f549f6348cb Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Mon, 30 Dec 2024 11:21:06 -0600 Subject: [PATCH 37/72] refactor(pinned-organizations): use next link for view all organizations button --- .../PinnedOrganizations.tsx | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/components/dashboard/PinnedOrganizations/PinnedOrganizations.tsx b/src/components/dashboard/PinnedOrganizations/PinnedOrganizations.tsx index d5765b556..dfc42f820 100644 --- a/src/components/dashboard/PinnedOrganizations/PinnedOrganizations.tsx +++ b/src/components/dashboard/PinnedOrganizations/PinnedOrganizations.tsx @@ -1,7 +1,7 @@ "use client"; import { Button, Flex, Grid, Icon, Stack, Text } from "@omnidev/sigil"; -import { useRouter } from "next/navigation"; +import Link from "next/link"; import { LuBuilding2, LuPlusCircle } from "react-icons/lu"; import { SkeletonArray } from "components/core"; @@ -17,7 +17,6 @@ import type { Organization } from "generated/graphql"; * Pinned organizations section. */ const PinnedOrganizations = () => { - const router = useRouter(); const { user } = useAuth(); const { @@ -66,15 +65,15 @@ const PinnedOrganizations = () => { - + + + {isError ? ( From f64db1e30a59dae6807af91d2ec6c4ba91764ba8 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Mon, 30 Dec 2024 11:55:04 -0600 Subject: [PATCH 38/72] chore: update comment regarding the dashboard aggregates total users query --- src/lib/graphql/queries/dashboardAggregates.query.graphql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/graphql/queries/dashboardAggregates.query.graphql b/src/lib/graphql/queries/dashboardAggregates.query.graphql index e845bee85..def029310 100644 --- a/src/lib/graphql/queries/dashboardAggregates.query.graphql +++ b/src/lib/graphql/queries/dashboardAggregates.query.graphql @@ -10,9 +10,10 @@ query DashboardAggregates($userId: UUID!) { ) { totalCount } - # TODO: discuss a better approach. Aggregate data with the plugin is difficult across M2M relationships. This is a temporary solution. + users( filter: { + # NB: this filters out users that are not associated with any organizations that the currently authenticated user is a part of. userOrganizations: { some: { organization: { From 042f468802b271da4bb19ea362739cb3d25d9140 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Mon, 30 Dec 2024 14:00:01 -0600 Subject: [PATCH 39/72] refactor(response): use updated db schema to display username --- .../dashboard/Response/Response.tsx | 2 +- src/generated/graphql.ts | 234 +++++++++--------- .../graphql/fragments/user.fragment.graphql | 34 --- src/lib/graphql/queries/posts.query.graphql | 2 +- .../queries/recentFeedback.query.graphql | 2 +- src/lib/graphql/queries/user.query.graphql | 5 - 6 files changed, 115 insertions(+), 164 deletions(-) delete mode 100644 src/lib/graphql/fragments/user.fragment.graphql delete mode 100644 src/lib/graphql/queries/user.query.graphql diff --git a/src/components/dashboard/Response/Response.tsx b/src/components/dashboard/Response/Response.tsx index bf183e603..96b0bdaa7 100644 --- a/src/components/dashboard/Response/Response.tsx +++ b/src/components/dashboard/Response/Response.tsx @@ -35,7 +35,7 @@ const Response = ({ feedback, type }: Props) => { - {feedback?.rowId} + {feedback?.user?.username} diff --git a/src/generated/graphql.ts b/src/generated/graphql.ts index dc8ac3c72..39b03af1b 100644 --- a/src/generated/graphql.ts +++ b/src/generated/graphql.ts @@ -491,6 +491,16 @@ export type DeleteUpvotePayloadUpvoteEdgeArgs = { orderBy?: Array; }; +/** All input for the `deleteUserByHidraId` mutation. */ +export type DeleteUserByHidraIdInput = { + /** + * 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; + hidraId: Scalars['UUID']['input']; +}; + /** All input for the `deleteUserById` mutation. */ export type DeleteUserByIdInput = { /** @@ -502,14 +512,14 @@ export type DeleteUserByIdInput = { id: Scalars['ID']['input']; }; -/** All input for the `deleteUserByWalletAddress` mutation. */ -export type DeleteUserByWalletAddressInput = { +/** All input for the `deleteUserByUsername` mutation. */ +export type DeleteUserByUsernameInput = { /** * 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; - walletAddress: Scalars['String']['input']; + username: Scalars['String']['input']; }; /** All input for the `deleteUser` mutation. */ @@ -622,10 +632,12 @@ export type Mutation = { deleteUpvoteByPostIdAndUserId?: Maybe; /** Deletes a single `User` using a unique key. */ deleteUser?: Maybe; + /** Deletes a single `User` using a unique key. */ + deleteUserByHidraId?: Maybe; /** Deletes a single `User` using its globally unique id. */ deleteUserById?: Maybe; /** Deletes a single `User` using a unique key. */ - deleteUserByWalletAddress?: Maybe; + deleteUserByUsername?: Maybe; /** Deletes a single `UserOrganization` using a unique key. */ deleteUserOrganizationByUserIdAndOrganizationId?: Maybe; /** Updates a single `Organization` using a unique key and a patch. */ @@ -656,10 +668,12 @@ export type Mutation = { updateUpvoteByPostIdAndUserId?: Maybe; /** Updates a single `User` using a unique key and a patch. */ updateUser?: Maybe; + /** Updates a single `User` using a unique key and a patch. */ + updateUserByHidraId?: Maybe; /** Updates a single `User` using its globally unique id and a patch. */ updateUserById?: Maybe; /** Updates a single `User` using a unique key and a patch. */ - updateUserByWalletAddress?: Maybe; + updateUserByUsername?: Maybe; /** Updates a single `UserOrganization` using a unique key and a patch. */ updateUserOrganizationByUserIdAndOrganizationId?: Maybe; }; @@ -785,6 +799,12 @@ export type MutationDeleteUserArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationDeleteUserByHidraIdArgs = { + input: DeleteUserByHidraIdInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationDeleteUserByIdArgs = { input: DeleteUserByIdInput; @@ -792,8 +812,8 @@ export type MutationDeleteUserByIdArgs = { /** The root mutation type which contains root level fields which mutate data. */ -export type MutationDeleteUserByWalletAddressArgs = { - input: DeleteUserByWalletAddressInput; +export type MutationDeleteUserByUsernameArgs = { + input: DeleteUserByUsernameInput; }; @@ -887,6 +907,12 @@ export type MutationUpdateUserArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationUpdateUserByHidraIdArgs = { + input: UpdateUserByHidraIdInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationUpdateUserByIdArgs = { input: UpdateUserByIdInput; @@ -894,8 +920,8 @@ export type MutationUpdateUserByIdArgs = { /** The root mutation type which contains root level fields which mutate data. */ -export type MutationUpdateUserByWalletAddressArgs = { - input: UpdateUserByWalletAddressInput; +export type MutationUpdateUserByUsernameArgs = { + input: UpdateUserByUsernameInput; }; @@ -1872,10 +1898,12 @@ export type Query = Node & { upvotes?: Maybe; /** Get a single `User`. */ user?: Maybe; + /** Get a single `User`. */ + userByHidraId?: Maybe; /** Reads a single `User` using its globally unique `ID`. */ userById?: Maybe; /** Get a single `User`. */ - userByWalletAddress?: Maybe; + userByUsername?: Maybe; /** Get a single `UserOrganization`. */ userOrganizationByUserIdAndOrganizationId?: Maybe; /** Reads and enables pagination through a set of `UserOrganization`. */ @@ -2029,6 +2057,12 @@ export type QueryUserArgs = { }; +/** The root query type which gives access points into the data universe. */ +export type QueryUserByHidraIdArgs = { + hidraId: Scalars['UUID']['input']; +}; + + /** The root query type which gives access points into the data universe. */ export type QueryUserByIdArgs = { id: Scalars['ID']['input']; @@ -2036,8 +2070,8 @@ export type QueryUserByIdArgs = { /** The root query type which gives access points into the data universe. */ -export type QueryUserByWalletAddressArgs = { - walletAddress: Scalars['String']['input']; +export type QueryUserByUsernameArgs = { + username: Scalars['String']['input']; }; @@ -2427,6 +2461,18 @@ export type UpdateUpvotePayloadUpvoteEdgeArgs = { orderBy?: Array; }; +/** All input for the `updateUserByHidraId` mutation. */ +export type UpdateUserByHidraIdInput = { + /** + * 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; + hidraId: Scalars['UUID']['input']; + /** An object where the defined keys will be set on the `User` being updated. */ + patch: UserPatch; +}; + /** All input for the `updateUserById` mutation. */ export type UpdateUserByIdInput = { /** @@ -2440,8 +2486,8 @@ export type UpdateUserByIdInput = { patch: UserPatch; }; -/** All input for the `updateUserByWalletAddress` mutation. */ -export type UpdateUserByWalletAddressInput = { +/** All input for the `updateUserByUsername` mutation. */ +export type UpdateUserByUsernameInput = { /** * An arbitrary string value with no semantic meaning. Will be included in the * payload verbatim. May be used to track mutations by the client. @@ -2449,7 +2495,7 @@ export type UpdateUserByWalletAddressInput = { clientMutationId?: InputMaybe; /** An object where the defined keys will be set on the `User` being updated. */ patch: UserPatch; - walletAddress: Scalars['String']['input']; + username: Scalars['String']['input']; }; /** All input for the `updateUser` mutation. */ @@ -2746,8 +2792,11 @@ export type UpvotePatch = { export type User = Node & { __typename?: 'User'; createdAt?: Maybe; + firstName?: Maybe; + hidraId: Scalars['UUID']['output']; /** A globally unique identifier. Can be used in various places throughout the system to identify this single value. */ id: Scalars['ID']['output']; + lastName?: Maybe; /** Reads and enables pagination through a set of `Post`. */ posts: PostConnection; rowId: Scalars['UUID']['output']; @@ -2756,7 +2805,7 @@ export type User = Node & { upvotes: UpvoteConnection; /** Reads and enables pagination through a set of `UserOrganization`. */ userOrganizations: UserOrganizationConnection; - walletAddress?: Maybe; + username?: Maybe; }; @@ -2806,12 +2855,18 @@ export type UserAggregates = { export type UserCondition = { /** Checks for equality with the object’s `createdAt` field. */ createdAt?: InputMaybe; + /** Checks for equality with the object’s `firstName` field. */ + firstName?: InputMaybe; + /** Checks for equality with the object’s `hidraId` field. */ + hidraId?: InputMaybe; + /** Checks for equality with the object’s `lastName` field. */ + lastName?: 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 `walletAddress` field. */ - walletAddress?: InputMaybe; + /** Checks for equality with the object’s `username` field. */ + username?: InputMaybe; }; /** A connection to a list of `User` values. */ @@ -2842,12 +2897,18 @@ export type UserDistinctCountAggregates = { __typename?: 'UserDistinctCountAggregates'; /** Distinct count of createdAt across the matching connection */ createdAt?: Maybe; + /** Distinct count of firstName across the matching connection */ + firstName?: Maybe; + /** Distinct count of hidraId across the matching connection */ + hidraId?: Maybe; + /** Distinct count of lastName across the matching connection */ + lastName?: Maybe; /** Distinct count of rowId across the matching connection */ rowId?: Maybe; /** Distinct count of updatedAt across the matching connection */ updatedAt?: Maybe; - /** Distinct count of walletAddress across the matching connection */ - walletAddress?: Maybe; + /** Distinct count of username across the matching connection */ + username?: Maybe; }; /** A `User` edge in the connection. */ @@ -2865,6 +2926,12 @@ export type UserFilter = { and?: InputMaybe>; /** Filter by the object’s `createdAt` field. */ createdAt?: InputMaybe; + /** Filter by the object’s `firstName` field. */ + firstName?: InputMaybe; + /** Filter by the object’s `hidraId` field. */ + hidraId?: InputMaybe; + /** Filter by the object’s `lastName` field. */ + lastName?: InputMaybe; /** Negates the expression. */ not?: InputMaybe; /** Checks for any expressions in this list. */ @@ -2885,8 +2952,8 @@ export type UserFilter = { userOrganizations?: InputMaybe; /** Some related `userOrganizations` exist. */ userOrganizationsExist?: InputMaybe; - /** Filter by the object’s `walletAddress` field. */ - walletAddress?: InputMaybe; + /** Filter by the object’s `username` field. */ + username?: InputMaybe; }; /** Grouping methods for `User` for usage during aggregation. */ @@ -2894,6 +2961,8 @@ export enum UserGroupBy { CreatedAt = 'CREATED_AT', CreatedAtTruncatedToDay = 'CREATED_AT_TRUNCATED_TO_DAY', CreatedAtTruncatedToHour = 'CREATED_AT_TRUNCATED_TO_HOUR', + FirstName = 'FIRST_NAME', + LastName = 'LAST_NAME', UpdatedAt = 'UPDATED_AT', UpdatedAtTruncatedToDay = 'UPDATED_AT_TRUNCATED_TO_DAY', UpdatedAtTruncatedToHour = 'UPDATED_AT_TRUNCATED_TO_HOUR' @@ -2962,15 +3031,24 @@ export type UserHavingVarianceSampleInput = { /** An input for mutations affecting `User` */ export type UserInput = { createdAt?: InputMaybe; + firstName?: InputMaybe; + hidraId: Scalars['UUID']['input']; + lastName?: InputMaybe; rowId?: InputMaybe; updatedAt?: InputMaybe; - walletAddress?: InputMaybe; + username?: InputMaybe; }; /** Methods to use when ordering `User`. */ export enum UserOrderBy { CreatedAtAsc = 'CREATED_AT_ASC', CreatedAtDesc = 'CREATED_AT_DESC', + FirstNameAsc = 'FIRST_NAME_ASC', + FirstNameDesc = 'FIRST_NAME_DESC', + HidraIdAsc = 'HIDRA_ID_ASC', + HidraIdDesc = 'HIDRA_ID_DESC', + LastNameAsc = 'LAST_NAME_ASC', + LastNameDesc = 'LAST_NAME_DESC', Natural = 'NATURAL', PostsCountAsc = 'POSTS_COUNT_ASC', PostsCountDesc = 'POSTS_COUNT_DESC', @@ -3006,6 +3084,8 @@ export enum UserOrderBy { UpvotesDistinctCountUpdatedAtDesc = 'UPVOTES_DISTINCT_COUNT_UPDATED_AT_DESC', UpvotesDistinctCountUserIdAsc = 'UPVOTES_DISTINCT_COUNT_USER_ID_ASC', UpvotesDistinctCountUserIdDesc = 'UPVOTES_DISTINCT_COUNT_USER_ID_DESC', + UsernameAsc = 'USERNAME_ASC', + UsernameDesc = 'USERNAME_DESC', UserOrganizationsCountAsc = 'USER_ORGANIZATIONS_COUNT_ASC', UserOrganizationsCountDesc = 'USER_ORGANIZATIONS_COUNT_DESC', UserOrganizationsDistinctCountCreatedAtAsc = 'USER_ORGANIZATIONS_DISTINCT_COUNT_CREATED_AT_ASC', @@ -3013,9 +3093,7 @@ export enum UserOrderBy { UserOrganizationsDistinctCountOrganizationIdAsc = 'USER_ORGANIZATIONS_DISTINCT_COUNT_ORGANIZATION_ID_ASC', UserOrganizationsDistinctCountOrganizationIdDesc = 'USER_ORGANIZATIONS_DISTINCT_COUNT_ORGANIZATION_ID_DESC', UserOrganizationsDistinctCountUserIdAsc = 'USER_ORGANIZATIONS_DISTINCT_COUNT_USER_ID_ASC', - UserOrganizationsDistinctCountUserIdDesc = 'USER_ORGANIZATIONS_DISTINCT_COUNT_USER_ID_DESC', - WalletAddressAsc = 'WALLET_ADDRESS_ASC', - WalletAddressDesc = 'WALLET_ADDRESS_DESC' + UserOrganizationsDistinctCountUserIdDesc = 'USER_ORGANIZATIONS_DISTINCT_COUNT_USER_ID_DESC' } export type UserOrganization = { @@ -3214,9 +3292,12 @@ export type UserOrganizationPatch = { /** Represents an update to a `User`. Fields that are set will be updated. */ export type UserPatch = { createdAt?: InputMaybe; + firstName?: InputMaybe; + hidraId?: InputMaybe; + lastName?: InputMaybe; rowId?: InputMaybe; updatedAt?: InputMaybe; - walletAddress?: InputMaybe; + username?: InputMaybe; }; /** A filter to be used against many `Post` object types. All fields are combined with a logical ‘and.’ */ @@ -3257,8 +3338,6 @@ export type UserToManyUserOrganizationFilter = { export type ProjectFragment = { __typename?: 'Project', createdAt?: Date | null, description?: string | null, id: string, image?: string | null, name?: string | null, organizationId: string, rowId: string, slug?: string | null, updatedAt?: Date | null, posts: { __typename?: 'PostConnection', nodes: Array<{ __typename?: 'Post', createdAt?: Date | null, description?: string | null, id: string, projectId: string, rowId: string, title?: string | null, updatedAt?: Date | null, userId: string } | null> } }; -export type UserFragment = { __typename?: 'User', createdAt?: Date | null, id: string, rowId: string, updatedAt?: Date | null, walletAddress?: string | null, userOrganizations: { __typename?: 'UserOrganizationConnection', nodes: Array<{ __typename?: 'UserOrganization', createdAt?: Date | null, organizationId: string, userId: string, organization?: { __typename?: 'Organization', id: string, createdAt?: Date | null, name?: string | null, rowId: string, slug?: string | null, updatedAt?: Date | null, projects: { __typename?: 'ProjectConnection', nodes: Array<{ __typename?: 'Project', slug?: string | null, rowId: string, organizationId: string, name?: string | null, image?: string | null, id: string, description?: string | null, createdAt?: Date | null } | null> } } | null } | null> } }; - export type CreatePostMutationVariables = Exact<{ postInput: PostInput; }>; @@ -3316,7 +3395,7 @@ export type PostsQueryVariables = Exact<{ }>; -export type PostsQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', nodes: Array<{ __typename?: 'Post', rowId: string, createdAt?: Date | null, title?: string | null, description?: string | null, user?: { __typename?: 'User', walletAddress?: string | null } | null, upvotes: { __typename?: 'UpvoteConnection', aggregates?: { __typename?: 'UpvoteAggregates', distinctCount?: { __typename?: 'UpvoteDistinctCountAggregates', rowId?: string | null } | null } | null, nodes: Array<{ __typename?: 'Upvote', rowId: string } | null> } } | null> } | null }; +export type PostsQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', nodes: Array<{ __typename?: 'Post', rowId: string, createdAt?: Date | null, title?: string | null, description?: string | null, user?: { __typename?: 'User', username?: string | null } | null, upvotes: { __typename?: 'UpvoteConnection', aggregates?: { __typename?: 'UpvoteAggregates', distinctCount?: { __typename?: 'UpvoteDistinctCountAggregates', rowId?: string | null } | null } | null, nodes: Array<{ __typename?: 'Upvote', rowId: string } | null> } } | null> } | null }; export type ProjectQueryVariables = Exact<{ organizationId: Scalars['UUID']['input']; @@ -3338,14 +3417,7 @@ export type RecentFeedbackQueryVariables = Exact<{ }>; -export type RecentFeedbackQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', nodes: Array<{ __typename?: 'Post', rowId: string, createdAt?: Date | null, title?: string | null, description?: string | null, user?: { __typename?: 'User', rowId: string } | null } | null> } | null }; - -export type UserQueryVariables = Exact<{ - walletAddress: Scalars['String']['input']; -}>; - - -export type UserQuery = { __typename?: 'Query', userByWalletAddress?: { __typename?: 'User', createdAt?: Date | null, id: string, rowId: string, updatedAt?: Date | null, walletAddress?: string | null, userOrganizations: { __typename?: 'UserOrganizationConnection', nodes: Array<{ __typename?: 'UserOrganization', createdAt?: Date | null, organizationId: string, userId: string, organization?: { __typename?: 'Organization', id: string, createdAt?: Date | null, name?: string | null, rowId: string, slug?: string | null, updatedAt?: Date | null, projects: { __typename?: 'ProjectConnection', nodes: Array<{ __typename?: 'Project', slug?: string | null, rowId: string, organizationId: string, name?: string | null, image?: string | null, id: string, description?: string | null, createdAt?: Date | null } | null> } } | null } | null> } } | null }; +export type RecentFeedbackQuery = { __typename?: 'Query', posts?: { __typename?: 'PostConnection', nodes: Array<{ __typename?: 'Post', rowId: string, createdAt?: Date | null, title?: string | null, description?: string | null, user?: { __typename?: 'User', rowId: string, username?: string | null } | null } | null> } | null }; export type WeeklyFeedbackQueryVariables = Exact<{ userId: Scalars['UUID']['input']; @@ -3381,42 +3453,6 @@ export const ProjectFragmentDoc = ` } } `; -export const UserFragmentDoc = ` - fragment User on User { - createdAt - id - rowId - updatedAt - walletAddress - userOrganizations { - nodes { - createdAt - organizationId - userId - organization { - id - createdAt - name - rowId - slug - updatedAt - projects { - nodes { - slug - rowId - organizationId - name - image - id - description - createdAt - } - } - } - } - } -} - `; export const CreatePostDocument = ` mutation CreatePost($postInput: PostInput!) { createPost(input: {post: $postInput}) { @@ -3673,7 +3709,7 @@ export const PostsDocument = ` title description user { - walletAddress + username } upvotes { aggregates { @@ -3845,6 +3881,7 @@ export const RecentFeedbackDocument = ` description user { rowId + username } } } @@ -3890,53 +3927,6 @@ export const useInfiniteRecentFeedbackQuery = < useInfiniteRecentFeedbackQuery.getKey = (variables: RecentFeedbackQueryVariables) => ['RecentFeedback.infinite', variables]; -export const UserDocument = ` - query User($walletAddress: String!) { - userByWalletAddress(walletAddress: $walletAddress) { - ...User - } -} - ${UserFragmentDoc}`; - -export const useUserQuery = < - TData = UserQuery, - TError = unknown - >( - variables: UserQueryVariables, - options?: Omit, 'queryKey'> & { queryKey?: UseQueryOptions['queryKey'] } - ) => { - - return useQuery( - { - queryKey: ['User', variables], - queryFn: useGraphqlClient(UserDocument).bind(null, variables), - ...options - } - )}; - -useUserQuery.getKey = (variables: UserQueryVariables) => ['User', variables]; - -export const useInfiniteUserQuery = < - TData = InfiniteData, - TError = unknown - >( - variables: UserQueryVariables, - options: Omit, 'queryKey'> & { queryKey?: UseInfiniteQueryOptions['queryKey'] } - ) => { - const query = useGraphqlClient(UserDocument) - return useInfiniteQuery( - (() => { - const { queryKey: optionsQueryKey, ...restOptions } = options; - return { - queryKey: optionsQueryKey ?? ['User.infinite', variables], - queryFn: (metaData) => query({...variables, ...(metaData.pageParam ?? {})}), - ...restOptions - } - })() - )}; - -useInfiniteUserQuery.getKey = (variables: UserQueryVariables) => ['User.infinite', variables]; - export const WeeklyFeedbackDocument = ` query WeeklyFeedback($userId: UUID!, $startDate: Datetime!) { posts( diff --git a/src/lib/graphql/fragments/user.fragment.graphql b/src/lib/graphql/fragments/user.fragment.graphql deleted file mode 100644 index 3f3a2b466..000000000 --- a/src/lib/graphql/fragments/user.fragment.graphql +++ /dev/null @@ -1,34 +0,0 @@ -fragment User on User { - createdAt - id - rowId - updatedAt - walletAddress - userOrganizations { - nodes { - createdAt - organizationId - userId - organization { - id - createdAt - name - rowId - slug - updatedAt - projects { - nodes { - slug - rowId - organizationId - name - image - id - description - createdAt - } - } - } - } - } -} diff --git a/src/lib/graphql/queries/posts.query.graphql b/src/lib/graphql/queries/posts.query.graphql index 5188007be..0845e29f7 100644 --- a/src/lib/graphql/queries/posts.query.graphql +++ b/src/lib/graphql/queries/posts.query.graphql @@ -6,7 +6,7 @@ query Posts($projectId: UUID!) { title description user { - walletAddress + username } upvotes { aggregates { diff --git a/src/lib/graphql/queries/recentFeedback.query.graphql b/src/lib/graphql/queries/recentFeedback.query.graphql index e1e6c5280..1fa27bae7 100644 --- a/src/lib/graphql/queries/recentFeedback.query.graphql +++ b/src/lib/graphql/queries/recentFeedback.query.graphql @@ -17,7 +17,7 @@ query RecentFeedback($userId: UUID!) { description user { rowId - # TODO: add `firstName` and `lastName` once the user is synced with the database + username } } } diff --git a/src/lib/graphql/queries/user.query.graphql b/src/lib/graphql/queries/user.query.graphql deleted file mode 100644 index fe21182f6..000000000 --- a/src/lib/graphql/queries/user.query.graphql +++ /dev/null @@ -1,5 +0,0 @@ -query User($walletAddress: String!) { - userByWalletAddress(walletAddress: $walletAddress) { - ...User - } -} From a64386efbe38678a9fe16b50ce86793e6364edd8 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Mon, 30 Dec 2024 15:02:41 -0600 Subject: [PATCH 40/72] refactor(projects): add query for organization details for breadcrumbs, generate metadata --- .../organizations/[organizationId]/page.tsx | 23 +++------------- .../[organizationId]/projects/page.tsx | 27 +++++++++++++++---- .../util/getOrganization/getOrganization.ts | 17 ++++++++++++ src/lib/util/index.ts | 1 + 4 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 src/lib/util/getOrganization/getOrganization.ts diff --git a/src/app/organizations/[organizationId]/page.tsx b/src/app/organizations/[organizationId]/page.tsx index 34052efa8..7a034ab82 100644 --- a/src/app/organizations/[organizationId]/page.tsx +++ b/src/app/organizations/[organizationId]/page.tsx @@ -1,35 +1,20 @@ -import request from "graphql-request"; import { notFound } from "next/navigation"; import { HiOutlineFolder } from "react-icons/hi2"; import { LuPlusCircle } from "react-icons/lu"; import { Page } from "components/layout"; import { OrganizationOverview } from "components/organization"; -import { OrganizationDocument } from "generated/graphql"; -import { API_BASE_URL, app } from "lib/config"; -import { getAuthSession } from "lib/util"; +import { app } from "lib/config"; +import { getAuthSession, getOrganization } from "lib/util"; -import type { - OrganizationQuery, - OrganizationQueryVariables, -} from "generated/graphql"; import type { Metadata } from "next"; -const fetchOrganization = async ( - organizationId: string -): Promise => - request({ - url: API_BASE_URL!, - document: OrganizationDocument, - variables: { rowId: organizationId } as OrganizationQueryVariables, - }); - export const generateMetadata = async ({ params, }: Props): Promise => { const { organizationId } = await params; - const { organization } = await fetchOrganization(organizationId); + const { organization } = await getOrganization(organizationId); return { title: `${organization?.name} | ${app.name}`, @@ -49,7 +34,7 @@ const OrganizationPage = async ({ params }: Props) => { const [session, { organization }] = await Promise.all([ getAuthSession(), - fetchOrganization(organizationId), + getOrganization(organizationId), ]); const breadcrumbs = [ diff --git a/src/app/organizations/[organizationId]/projects/page.tsx b/src/app/organizations/[organizationId]/projects/page.tsx index 7049b7e92..9ce31c510 100644 --- a/src/app/organizations/[organizationId]/projects/page.tsx +++ b/src/app/organizations/[organizationId]/projects/page.tsx @@ -4,7 +4,21 @@ import { LuPlusCircle } from "react-icons/lu"; import { Page } from "components/layout"; import { ProjectsOverview } from "components/project"; import { app } from "lib/config"; -import { getAuthSession } from "lib/util"; +import { getAuthSession, getOrganization } from "lib/util"; + +import type { Metadata } from "next"; + +export const generateMetadata = async ({ + params, +}: Props): Promise => { + const { organizationId } = await params; + + const { organization } = await getOrganization(organizationId); + + return { + title: `${organization?.name} Projects | ${app.name}`, + }; +}; interface Props { /** Projects page params. */ @@ -16,7 +30,11 @@ interface Props { */ const ProjectsPage = async ({ params }: Props) => { const { organizationId } = await params; - const session = await getAuthSession(); + + const [session, { organization }] = await Promise.all([ + getAuthSession(), + getOrganization(organizationId), + ]); const breadcrumbs = [ { @@ -24,8 +42,7 @@ const ProjectsPage = async ({ params }: Props) => { href: "/organizations", }, { - // TODO: Use actual organization name here instead of ID - label: organizationId, + label: organization?.name ?? organizationId, href: `/organizations/${organizationId}`, }, { @@ -33,7 +50,7 @@ const ProjectsPage = async ({ params }: Props) => { }, ]; - if (!session) notFound(); + if (!session || !organization) notFound(); return ( => + request({ + url: API_BASE_URL!, + document: OrganizationDocument, + variables: { rowId: organizationId } as OrganizationQueryVariables, + }); + +export default getOrganization; \ No newline at end of file diff --git a/src/lib/util/index.ts b/src/lib/util/index.ts index 47956c645..e4bfdf60f 100644 --- a/src/lib/util/index.ts +++ b/src/lib/util/index.ts @@ -1,4 +1,5 @@ export { default as getAuthSession } from "./getAuthSession/getAuthSession"; +export { default as getOrganization } from "./getOrganization/getOrganization"; export { default as getResponseTypeColor, type ResponseType, From 230c87c01537c90d2a816f7d3236d0a097e58384 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Mon, 30 Dec 2024 15:03:13 -0600 Subject: [PATCH 41/72] chore: format --- src/lib/util/getOrganization/getOrganization.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/util/getOrganization/getOrganization.ts b/src/lib/util/getOrganization/getOrganization.ts index e9b8fe729..0cf2799cc 100644 --- a/src/lib/util/getOrganization/getOrganization.ts +++ b/src/lib/util/getOrganization/getOrganization.ts @@ -3,7 +3,10 @@ import { request } from "graphql-request"; import { API_BASE_URL } from "lib/config"; import { OrganizationDocument } from "generated/graphql"; -import type { OrganizationQuery, OrganizationQueryVariables } from "generated/graphql"; +import type { + OrganizationQuery, + OrganizationQueryVariables, +} from "generated/graphql"; const getOrganization = async ( organizationId: string @@ -14,4 +17,4 @@ const getOrganization = async ( variables: { rowId: organizationId } as OrganizationQueryVariables, }); -export default getOrganization; \ No newline at end of file +export default getOrganization; From 5b792c86bd0ab0bae88bedaac081d84d23ccef59 Mon Sep 17 00:00:00 2001 From: hobbescodes <87732294+hobbescodes@users.noreply.github.com> Date: Mon, 30 Dec 2024 15:40:45 -0600 Subject: [PATCH 42/72] feature(projects): update route to use dynamic projects query --- .../[organizationId]/projects/page.tsx | 6 +- .../project/ProjectFilters/ProjectFilters.tsx | 15 +---- .../project/ProjectList/ProjectList.tsx | 64 +++++++++---------- .../ProjectListItem/ProjectListItem.tsx | 55 ++++++---------- .../ProjectsOverview/ProjectsOverview.tsx | 64 ------------------- src/components/project/index.ts | 6 +- src/generated/graphql.ts | 23 ++++--- .../graphql/queries/projects.query.graphql | 8 ++- 8 files changed, 75 insertions(+), 166 deletions(-) delete mode 100644 src/components/project/ProjectsOverview/ProjectsOverview.tsx diff --git a/src/app/organizations/[organizationId]/projects/page.tsx b/src/app/organizations/[organizationId]/projects/page.tsx index 9ce31c510..0528079e5 100644 --- a/src/app/organizations/[organizationId]/projects/page.tsx +++ b/src/app/organizations/[organizationId]/projects/page.tsx @@ -2,7 +2,7 @@ import { notFound } from "next/navigation"; import { LuPlusCircle } from "react-icons/lu"; import { Page } from "components/layout"; -import { ProjectsOverview } from "components/project"; +import { ProjectFilters, ProjectList } from "components/project"; import { app } from "lib/config"; import { getAuthSession, getOrganization } from "lib/util"; @@ -67,7 +67,9 @@ const ProjectsPage = async ({ params }: Props) => { ], }} > - + + + ); }; diff --git a/src/components/project/ProjectFilters/ProjectFilters.tsx b/src/components/project/ProjectFilters/ProjectFilters.tsx index 2c1bdb1eb..10b99ad19 100644 --- a/src/components/project/ProjectFilters/ProjectFilters.tsx +++ b/src/components/project/ProjectFilters/ProjectFilters.tsx @@ -13,21 +13,12 @@ import { useSearchParams } from "lib/hooks"; const STATUSES = ["Active", "Beta", "Inactive"]; -interface Props { - /** Whether the data is loading. */ - isLoading?: boolean; - /** Whether an error was encountered while loading the data. */ - isError?: boolean; -} - /** * Project filters. */ -const ProjectFilters = ({ isLoading = true, isError = false }: Props) => { +const ProjectFilters = () => { const [{ status, search }, setSearchParams] = useSearchParams(); - const isFilterDisabled = isLoading || isError; - return ( @@ -40,10 +31,10 @@ const ProjectFilters = ({ isLoading = true, isError = false }: Props) => { search: e.target.value.length ? e.target.value.toLowerCase() : "", }) } - disabled={isFilterDisabled} /> + {/* TODO: discuss if this should be removed for now, or if we should update the db schSchema to include project statuses. */} { } /> - - {/* TODO: discuss if this should be removed for now, or if we should update the db schSchema to include project statuses. */} - -