added user overview
This commit is contained in:
parent
b5738c610d
commit
2ac082a4e4
5 changed files with 107 additions and 14 deletions
|
|
@ -7,6 +7,16 @@ export const sessionFragment = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const userFragment = gql`
|
||||||
|
fragment User on User {
|
||||||
|
id
|
||||||
|
username
|
||||||
|
description
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const bookFragment = gql`
|
export const bookFragment = gql`
|
||||||
fragment Book on Book {
|
fragment Book on Book {
|
||||||
id
|
id
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
import { ApolloError, ApolloQueryResult, gql, OperationVariables, useQuery } from '@apollo/client';
|
import { ApolloError, ApolloQueryResult, gql, OperationVariables, useQuery } from '@apollo/client';
|
||||||
import { UserOneQuery } from '@generated/types';
|
import { UserOneQuery } from '@generated/types';
|
||||||
|
import { userFragment } from '../fragments';
|
||||||
|
|
||||||
export function useUserOneQuery({ id, skip }: { id?: string; skip?: boolean }): {
|
export function useUserOneQuery({
|
||||||
|
input,
|
||||||
|
skip
|
||||||
|
}: {
|
||||||
|
readonly input: { readonly id: string } | { readonly username: string };
|
||||||
|
readonly skip?: boolean;
|
||||||
|
}): {
|
||||||
data: UserOneQuery | undefined;
|
data: UserOneQuery | undefined;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: ApolloError | undefined;
|
error: ApolloError | undefined;
|
||||||
|
|
@ -10,7 +17,7 @@ export function useUserOneQuery({ id, skip }: { id?: string; skip?: boolean }):
|
||||||
) => Promise<ApolloQueryResult<UserOneQuery>>;
|
) => Promise<ApolloQueryResult<UserOneQuery>>;
|
||||||
} {
|
} {
|
||||||
const { data, loading, error, refetch } = useQuery<UserOneQuery>(query, {
|
const { data, loading, error, refetch } = useQuery<UserOneQuery>(query, {
|
||||||
variables: { id },
|
variables: { input },
|
||||||
skip
|
skip
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|
@ -22,12 +29,10 @@ export function useUserOneQuery({ id, skip }: { id?: string; skip?: boolean }):
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = gql`
|
const query = gql`
|
||||||
query UserOne($id: ID!) {
|
query UserOne($input: UserOneInput!) {
|
||||||
userOne(id: $id) {
|
userOne(input: $input) {
|
||||||
id
|
...User
|
||||||
username
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
${userFragment}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import {Stack} from '@chakra-ui/react';
|
import { Avatar } from '@app/client/components/ui/avatar';
|
||||||
import {createFileRoute} from '@tanstack/react-router';
|
import { toFormattedDate } from '@app/client/helpers';
|
||||||
|
import { useUserOneQuery } from '@app/client/hooks/useUserOneQuery';
|
||||||
|
import { Card, Heading, HStack, SkeletonCircle, SkeletonText, Stack, Text } from '@chakra-ui/react';
|
||||||
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
|
||||||
export const Route = createFileRoute('/profile/$username')({
|
export const Route = createFileRoute('/profile/$username')({
|
||||||
component: RouteComponent
|
component: RouteComponent
|
||||||
|
|
@ -7,5 +10,67 @@ export const Route = createFileRoute('/profile/$username')({
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { username } = Route.useParams();
|
const { username } = Route.useParams();
|
||||||
return <Stack>{`Hello, "${username}"!`}</Stack>;
|
|
||||||
|
const { data } = useUserOneQuery({
|
||||||
|
input: {
|
||||||
|
username
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data == null) return <UserOverviewSkeleton />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack width="100%" justifyContent="center" alignItems="center" padding={3}>
|
||||||
|
<UserOverview user={{ ...data.userOne }} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserOverview({
|
||||||
|
user
|
||||||
|
}: {
|
||||||
|
readonly user: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly username: string;
|
||||||
|
readonly description?: string | null;
|
||||||
|
readonly createdAt: string;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Stack width={{ base: '100%', md: '80%' }} maxWidth="840px" gap={3}>
|
||||||
|
<Stack direction="row" alignItems="start">
|
||||||
|
<Avatar size="2xl" fallback={user.username[0]} />
|
||||||
|
<Stack width="100%">
|
||||||
|
<Heading color="colorPalette.400" size="2xl" overflow="auto">
|
||||||
|
{user.username}
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
<Stack direction="row">
|
||||||
|
<Text fontWeight="bold">joined:</Text>
|
||||||
|
<Text color="fg.muted">{toFormattedDate({ value: user.createdAt })}</Text>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
<Card.Root variant="subtle" width="100%">
|
||||||
|
<Card.Body paddingX="3" paddingY="1">
|
||||||
|
<Text>{user.description ?? 'No user description =('}</Text>
|
||||||
|
</Card.Body>
|
||||||
|
</Card.Root>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function UserOverviewSkeleton() {
|
||||||
|
return (
|
||||||
|
<Stack width="100%" justifyContent="center" alignItems="center" padding={3}>
|
||||||
|
<Stack width={{ base: '100%', md: '80%' }} maxWidth="840px">
|
||||||
|
<SkeletonCircle width={70} height={70} />
|
||||||
|
<Stack gap="6" maxWidth="100%">
|
||||||
|
<HStack width="full">
|
||||||
|
<SkeletonText noOfLines={5} />
|
||||||
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,17 @@ import { toUserSchema } from './mappers/userMapper';
|
||||||
|
|
||||||
export async function UserOneResolver(
|
export async function UserOneResolver(
|
||||||
_: any,
|
_: any,
|
||||||
{ id }: QueryUserOneArgs,
|
{ input }: QueryUserOneArgs,
|
||||||
{ identity }: IdentityContext
|
{ identity }: IdentityContext
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
const model = await prisma.user.findUniqueOrThrow({ where: { id } });
|
if (input.id == null && input.username == null) throw new Error('Invalid user input.');
|
||||||
|
if (input.id != null && input.username != null) throw new Error('Invalid user input.');
|
||||||
|
|
||||||
|
const model = await prisma.user.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id: input.id != null ? input.id : undefined,
|
||||||
|
username: input.username != null ? input.username : undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
return toUserSchema(model);
|
return toUserSchema(model);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ scalar JSON
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
identity: Identity!
|
identity: Identity!
|
||||||
userOne(id: ID!): User!
|
userOne(input: UserOneInput!): User!
|
||||||
bookOne(id: ID!): Book!
|
bookOne(id: ID!): Book!
|
||||||
bookMany(input: BookManyInput!): BookManyOutput!
|
bookMany(input: BookManyInput!): BookManyOutput!
|
||||||
bookChapterOne(id: ID!): BookChapter!
|
bookChapterOne(id: ID!): BookChapter!
|
||||||
|
|
@ -58,6 +58,11 @@ type User {
|
||||||
updatedAt: String!
|
updatedAt: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input UserOneInput {
|
||||||
|
id: ID
|
||||||
|
username: String
|
||||||
|
}
|
||||||
|
|
||||||
input PaginationFilterInput {
|
input PaginationFilterInput {
|
||||||
terms: String
|
terms: String
|
||||||
offset: Int
|
offset: Int
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue