Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,478 changes: 746 additions & 732 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-query": "^5.37.1",
"axios": "^1.4.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
Expand Down
9 changes: 8 additions & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createBrowserRouter, RouterProvider } from "react-router-dom";

import { Layout } from "@/components/layout";
import { Feed } from "@/pages/feed";
import { UserProfile } from "@/pages/user-profile";

const queryClient = new QueryClient();

const router = createBrowserRouter([
{
path: "/",
Expand All @@ -22,5 +25,9 @@ const router = createBrowserRouter([
]);

export function App() {
return <RouterProvider router={router} />;
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
}
76 changes: 0 additions & 76 deletions src/application/get-user-profile/get-user-profile.test.ts

This file was deleted.

38 changes: 13 additions & 25 deletions src/application/get-user-profile/get-user-profile.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
import { useCallback } from "react";

import UserService from "@/infrastructure/user";
import { useGetUser } from "../queries/use-get-user";
import { useGetUserShouts } from "../queries/use-get-user-shouts";

interface GetUserProfileInput {
handle: string;
}

const dependencies = {
getUser: UserService.getUser,
getUserShouts: UserService.getUserShouts,
};

export async function getUserProfile(
{ handle }: GetUserProfileInput,
{ getUser, getUserShouts }: typeof dependencies
) {
const [user, { shouts, images }] = await Promise.all([
getUser(handle),
getUserShouts(handle),
]);
return { user, shouts, images };
handle?: string;
}

export function useGetUserProfile() {
return useCallback(
(input: GetUserProfileInput) => getUserProfile(input, dependencies),
[]
);
export function useGetUserProfile({ handle }: GetUserProfileInput) {
const user = useGetUser({ handle });
const userShouts = useGetUserShouts({ handle });
return {
user: user.data,
shouts: userShouts.data?.shouts,
images: userShouts.data?.images,
isLoading: user.isLoading || userShouts.isLoading,
error: user.error || userShouts.error,
};
}
15 changes: 15 additions & 0 deletions src/application/mutations/use-create-shout-reply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useMutation } from "@tanstack/react-query";

import ShoutService from "@/infrastructure/shout";

interface CreateShoutReplyInput {
shoutId: string;
replyId: string;
}

export function useCreateShoutReply() {
return useMutation({
mutationFn: (input: CreateShoutReplyInput) =>
ShoutService.createReply(input),
});
}
14 changes: 14 additions & 0 deletions src/application/mutations/use-create-shout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useMutation } from "@tanstack/react-query";

import ShoutService from "@/infrastructure/shout";

interface CreateShoutInput {
message: string;
imageId?: string;
}

export function useCreateShout() {
return useMutation({
mutationFn: (input: CreateShoutInput) => ShoutService.createShout(input),
});
}
20 changes: 20 additions & 0 deletions src/application/mutations/use-login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";

import AuthService from "@/infrastructure/auth";

import { getQueryKey as getFeedQueryKey } from "../queries/use-get-feed";
import { getQueryKey as getMeQueryKey } from "../queries/use-get-me";

type Credentials = Parameters<(typeof AuthService)["login"]>[0];

export function useLogin() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: (credentials: Credentials) => AuthService.login(credentials),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: getMeQueryKey() });
queryClient.invalidateQueries({ queryKey: getFeedQueryKey() });
},
});
}
18 changes: 18 additions & 0 deletions src/application/mutations/use-logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";

import AuthService from "@/infrastructure/auth";

import { getQueryKey as getFeedQueryKey } from "../queries/use-get-feed";
import { getQueryKey as getMeQueryKey } from "../queries/use-get-me";

export function useLogout() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: () => AuthService.logout(),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: getMeQueryKey() });
queryClient.invalidateQueries({ queryKey: getFeedQueryKey() });
},
});
}
9 changes: 9 additions & 0 deletions src/application/mutations/use-save-image.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useMutation } from "@tanstack/react-query";

import MediaService from "@/infrastructure/media";

export function useSaveImage() {
return useMutation({
mutationFn: (file: File) => MediaService.saveImage(file),
});
}
14 changes: 14 additions & 0 deletions src/application/queries/use-get-feed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useQuery } from "@tanstack/react-query";

import FeedService from "@/infrastructure/feed";

export function getQueryKey() {
return ["feed"];
}

export function useGetFeed() {
return useQuery({
queryKey: getQueryKey(),
queryFn: () => FeedService.getFeed(),
});
}
14 changes: 14 additions & 0 deletions src/application/queries/use-get-me.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useQuery } from "@tanstack/react-query";

import UserService from "@/infrastructure/user";

export function getQueryKey() {
return ["me"];
}

export function useGetMe() {
return useQuery({
queryKey: getQueryKey(),
queryFn: () => UserService.getMe(),
});
}
18 changes: 18 additions & 0 deletions src/application/queries/use-get-user-shouts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useQuery } from "@tanstack/react-query";

import UserService from "@/infrastructure/user";

interface GetUserShoutsInput {
handle?: string;
}

export function getQueryKey(handle?: string) {
return ["user-shouts", handle];
}

export function useGetUserShouts({ handle }: GetUserShoutsInput) {
return useQuery({
queryKey: getQueryKey(handle),
queryFn: () => UserService.getUserShouts(handle),
});
}
18 changes: 18 additions & 0 deletions src/application/queries/use-get-user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useQuery } from "@tanstack/react-query";

import UserService from "@/infrastructure/user";

interface GetUserInput {
handle?: string;
}

export function getQueryKey(handle?: string) {
return ["user", handle];
}

export function useGetUser({ handle }: GetUserInput) {
return useQuery({
queryKey: getQueryKey(handle),
queryFn: () => UserService.getUser(handle),
});
}
32 changes: 12 additions & 20 deletions src/application/reply-to-shout/reply-to-shout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,59 +31,51 @@ const mockRecipient = {
followerIds: [],
};

const mockGetMe = vitest.fn().mockResolvedValue(mockMe);
const mockGetUser = vitest.fn().mockResolvedValue(mockRecipient);
const mockSaveImage = vitest.fn().mockResolvedValue({ id: imageId });
const mockCreateShout = vitest.fn().mockResolvedValue({ id: newShoutId });
const mockCreateReply = vitest.fn();

const mockDependencies = {
getMe: mockGetMe,
getUser: mockGetUser,
me: mockMe,
recipient: mockRecipient,
saveImage: mockSaveImage,
createShout: mockCreateShout,
createReply: mockCreateReply,
};

describe("replyToShout", () => {
beforeEach(() => {
Object.values(mockDependencies).forEach((mock) => mock.mockClear());
vitest.clearAllMocks();
});

it("should return an error if the user has made too many shouts", async () => {
mockGetMe.mockResolvedValueOnce({
...mockMe,
numShoutsPastDay: MAX_NUM_SHOUTS_PER_DAY,
});

const result = await replyToShout(
{ recipientHandle, shoutId, message, files },
mockDependencies
{
...mockDependencies,
me: { ...mockMe, numShoutsPastDay: MAX_NUM_SHOUTS_PER_DAY },
}
);

expect(result).toEqual({ error: ErrorMessages.TooManyShouts });
});

it("should return an error if the recipient does not exist", async () => {
mockGetUser.mockResolvedValueOnce(undefined);

const result = await replyToShout(
{ recipientHandle, shoutId, message, files },
mockDependencies
{ ...mockDependencies, recipient: null }
);

expect(result).toEqual({ error: ErrorMessages.RecipientNotFound });
});

it("should return an error if the recipient has blocked the author", async () => {
mockGetUser.mockResolvedValueOnce({
...mockRecipient,
blockedUserIds: [mockMe.id],
});

const result = await replyToShout(
{ recipientHandle, shoutId, message, files },
mockDependencies
{
...mockDependencies,
recipient: { ...mockRecipient, blockedUserIds: [mockMe.id] },
}
);

expect(result).toEqual({ error: ErrorMessages.AuthorBlockedByRecipient });
Expand Down
Loading