Skip to content

Commit

Permalink
more structural improvements (shared api types, better error boundaries)
Browse files Browse the repository at this point in the history
  • Loading branch information
alan2207 committed May 18, 2024
1 parent bd85984 commit f705e48
Show file tree
Hide file tree
Showing 27 changed files with 133 additions and 127 deletions.
5 changes: 5 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ module.exports = {
'error',
{
zones: [
{
target: './src/features/auth',
from: './src/features',
except: ['./auth'],
},
{
target: './src/features/comments',
from: './src/features',
Expand Down
9 changes: 7 additions & 2 deletions docs/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ src/features/awesome-feature
|
+-- stores # state stores for a specific feature
|
+-- types # typescript types for TS specific feature domain
+-- types # typescript types used within the feature
|
+-- utils # utility functions for a specific feature
```

Do not import across the features. Instead, compose different features at the application level in the `routes` folder. This way, you can ensure that each feature is independent and can be easily moved or removed without affecting other parts of the application.
It might be a bad idea to import across the features. Instead, compose different features at the application level in the routes/pages. This way, you can ensure that each feature is independent and can be easily moved or removed without affecting other parts of the application and also makes the codebase less convoluted.

To forbid cross-feature imports, you can use ESLint:

Expand All @@ -61,6 +61,11 @@ To forbid cross-feature imports, you can use ESLint:
'error',
{
zones: [
{
target: './src/features/auth',
from: './src/features',
except: ['./auth'],
},
{
target: './src/features/comments',
from: './src/features',
Expand Down
18 changes: 18 additions & 0 deletions src/components/errors/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Button } from '../ui/button';

export const MainErrorFallback = () => {
return (
<div
className="flex h-screen w-screen flex-col items-center justify-center text-red-500"
role="alert"
>
<h2 className="text-lg font-semibold">Ooops, something went wrong :( </h2>
<Button
className="mt-4"
onClick={() => window.location.assign(window.location.origin)}
>
Refresh
</Button>
</div>
);
};
2 changes: 1 addition & 1 deletion src/components/ui/table/table.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ArchiveX } from 'lucide-react';
import * as React from 'react';

import { BaseEntity } from '@/types';
import { BaseEntity } from '@/types/api';
import { cn } from '@/utils/cn';

const TableElement = React.forwardRef<
Expand Down
10 changes: 9 additions & 1 deletion src/features/auth/components/__tests__/register-form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ test('should register new user and call onSuccess cb which should navigate the u

const onSuccess = vi.fn();

await renderApp(<RegisterForm onSuccess={onSuccess} />, { user: null });
await renderApp(
<RegisterForm
onSuccess={onSuccess}
chooseTeam={false}
setChooseTeam={() => {}}
teams={[]}
/>,
{ user: null },
);

await userEvent.type(screen.getByLabelText(/first name/i), newUser.firstName);
await userEvent.type(screen.getByLabelText(/last name/i), newUser.lastName);
Expand Down
23 changes: 12 additions & 11 deletions src/features/auth/components/register-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,26 @@ import { Link, useSearchParams } from 'react-router-dom';

import { Button } from '@/components/ui/button';
import { Form, Input, Select, Label, Switch } from '@/components/ui/form';
import { useTeams } from '@/features/teams/api/get-teams';
import { useRegister, registerInputSchema } from '@/lib/auth';
import { Team } from '@/types/api';

type RegisterFormProps = {
onSuccess: () => void;
chooseTeam: boolean;
setChooseTeam: () => void;
teams?: Team[];
};

export const RegisterForm = ({ onSuccess }: RegisterFormProps) => {
export const RegisterForm = ({
onSuccess,
chooseTeam,
setChooseTeam,
teams,
}: RegisterFormProps) => {
const registering = useRegister({ onSuccess });
const [chooseTeam, setChooseTeam] = React.useState(false);
const [searchParams] = useSearchParams();
const redirectTo = searchParams.get('redirectTo');

const teamsQuery = useTeams({
queryConfig: {
enabled: chooseTeam,
},
});

return (
<div>
<Form
Expand Down Expand Up @@ -72,12 +73,12 @@ export const RegisterForm = ({ onSuccess }: RegisterFormProps) => {
<Label htmlFor="airplane-mode">Join Existing Team</Label>
</div>

{chooseTeam && teamsQuery.data ? (
{chooseTeam && teams ? (
<Select
label="Team"
error={formState.errors['teamId']}
registration={register('teamId')}
options={teamsQuery?.data?.map((team) => ({
options={teams?.map((team) => ({
label: team.name,
value: team.id,
}))}
Expand Down
3 changes: 1 addition & 2 deletions src/features/comments/api/create-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { z } from 'zod';

import { api } from '@/lib/api-client';
import { MutationConfig } from '@/lib/react-query';

import { Comment } from '../types';
import { Comment } from '@/types/api';

import { getCommentsQueryOptions } from './get-comments';

Expand Down
3 changes: 1 addition & 2 deletions src/features/comments/api/get-comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { queryOptions, useQuery } from '@tanstack/react-query';

import { api } from '@/lib/api-client';
import { QueryConfig } from '@/lib/react-query';

import { Comment } from '../types';
import { Comment } from '@/types/api';

export const getComments = ({
discussionId,
Expand Down
3 changes: 2 additions & 1 deletion src/features/comments/components/comments-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { ArchiveX } from 'lucide-react';

import { MDPreview } from '@/components/ui/md-preview';
import { Spinner } from '@/components/ui/spinner';
import { User, useUser } from '@/lib/auth';
import { useUser } from '@/lib/auth';
import { POLICIES, Authorization } from '@/lib/authorization';
import { User } from '@/types/api';
import { formatDate } from '@/utils/format';

import { useComments } from '../api/get-comments';
Expand Down
14 changes: 0 additions & 14 deletions src/features/comments/types/index.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/features/discussions/api/create-discussion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { z } from 'zod';

import { api } from '@/lib/api-client';
import { MutationConfig } from '@/lib/react-query';

import { Discussion } from '../types';
import { Discussion } from '@/types/api';

import { getDiscussionsQueryOptions } from './get-discussions';

Expand Down
3 changes: 1 addition & 2 deletions src/features/discussions/api/get-discussion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { useQuery, queryOptions } from '@tanstack/react-query';

import { api } from '@/lib/api-client';
import { QueryConfig } from '@/lib/react-query';

import { Discussion } from '../types';
import { Discussion } from '@/types/api';

export const getDiscussion = ({
discussionId,
Expand Down
3 changes: 1 addition & 2 deletions src/features/discussions/api/get-discussions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { queryOptions, useQuery } from '@tanstack/react-query';

import { api } from '@/lib/api-client';
import { QueryConfig } from '@/lib/react-query';

import { Discussion } from '../types';
import { Discussion } from '@/types/api';

export const getDiscussions = (): Promise<Discussion[]> => {
return api.get('/discussions');
Expand Down
3 changes: 1 addition & 2 deletions src/features/discussions/api/update-discussion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { z } from 'zod';

import { api } from '@/lib/api-client';
import { MutationConfig } from '@/lib/react-query';

import { Discussion } from '../types';
import { Discussion } from '@/types/api';

import { getDiscussionsQueryOptions } from './get-discussion';

Expand Down
15 changes: 0 additions & 15 deletions src/features/discussions/types/index.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/features/teams/api/get-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { queryOptions, useQuery } from '@tanstack/react-query';

import { api } from '@/lib/api-client';
import { QueryConfig } from '@/lib/react-query';

import { Team } from '../types';
import { Team } from '@/types/api';

export const getTeams = (): Promise<Team[]> => {
return api.get('/teams');
Expand Down
6 changes: 0 additions & 6 deletions src/features/teams/types/index.ts

This file was deleted.

3 changes: 1 addition & 2 deletions src/features/users/api/get-users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { queryOptions, useQuery } from '@tanstack/react-query';

import { api } from '@/lib/api-client';
import { QueryConfig } from '@/lib/react-query';

import { User } from '../types';
import { User } from '@/types/api';

export const getUsers = (): Promise<User[]> => {
return api.get(`/users`);
Expand Down
10 changes: 0 additions & 10 deletions src/features/users/types/index.ts

This file was deleted.

20 changes: 3 additions & 17 deletions src/lib/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,13 @@ import { configureAuth } from 'react-query-auth';
import { Navigate, useLocation } from 'react-router-dom';
import { z } from 'zod';

import { Entity } from '@/types';
import { AuthResponse, User } from '@/types/api';

import { api } from './api-client';

// api call definitions for auth (types, schemas, requests):
// these are not part of features as this is a shared module across features

export type User = Entity<{
firstName: string;
lastName: string;
email: string;
role: 'ADMIN' | 'USER';
teamId: string;
bio: string;
}>;

export type UserResponse = {
jwt: string;
user: User;
};

const getUser = (): Promise<User> => {
return api.get('/auth/me');
};
Expand All @@ -37,7 +23,7 @@ export const loginInputSchema = z.object({
});

export type LoginInput = z.infer<typeof loginInputSchema>;
const loginWithEmailAndPassword = (data: LoginInput): Promise<UserResponse> => {
const loginWithEmailAndPassword = (data: LoginInput): Promise<AuthResponse> => {
return api.post('/auth/login', data);
};

Expand Down Expand Up @@ -66,7 +52,7 @@ export type RegisterInput = z.infer<typeof registerInputSchema>;

const registerWithEmailAndPassword = (
data: RegisterInput,
): Promise<UserResponse> => {
): Promise<AuthResponse> => {
return api.post('/auth/register', data);
};

Expand Down
3 changes: 1 addition & 2 deletions src/lib/authorization.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';

import { type Comment } from '@/features/comments/types';
import { type User } from '@/features/users/types';
import { Comment, User } from '@/types/api';

import { useUser } from './auth';

Expand Down
21 changes: 2 additions & 19 deletions src/providers/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,12 @@ import * as React from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { HelmetProvider } from 'react-helmet-async';

import { Button } from '@/components/ui/button';
import { MainErrorFallback } from '@/components/errors/main';
import { Notifications } from '@/components/ui/notifications';
import { Spinner } from '@/components/ui/spinner';
import { AuthLoader } from '@/lib/auth';
import { queryClient } from '@/lib/react-query';

const ErrorFallback = () => {
return (
<div
className="flex h-screen w-screen flex-col items-center justify-center text-red-500"
role="alert"
>
<h2 className="text-lg font-semibold">Ooops, something went wrong :( </h2>
<Button
className="mt-4"
onClick={() => window.location.assign(window.location.origin)}
>
Refresh
</Button>
</div>
);
};

type AppProviderProps = {
children: React.ReactNode;
};
Expand All @@ -40,7 +23,7 @@ export const AppProvider = ({ children }: AppProviderProps) => {
</div>
}
>
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ErrorBoundary FallbackComponent={MainErrorFallback}>
<HelmetProvider>
<QueryClientProvider client={queryClient}>
{import.meta.env.DEV && <ReactQueryDevtools />}
Expand Down
Loading

0 comments on commit f705e48

Please sign in to comment.