Skip to content

Commit

Permalink
chore: cleanup code (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
rifandani authored Jun 30, 2024
1 parent 3de49eb commit 7cf0fb1
Show file tree
Hide file tree
Showing 48 changed files with 1,548 additions and 962 deletions.
2 changes: 1 addition & 1 deletion .vscode/app.code-snippets
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
" const passwordInput = page.getByRole('textbox', { name: /password/i });",
" const submitBtn = page.getByRole('button', { name: /login/i });",
" ",
" await page.waitForURL('/login');",
" await page.waitForURL(/\/login/);",
" await expect(usernameInput).toBeVisible();",
" await expect(passwordInput).toBeVisible();",
" await expect(submitBtn).toBeVisible();",
Expand Down
8 changes: 4 additions & 4 deletions e2e/_helper.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { LoginApiResponseSchema } from '#auth/apis/auth.api';
import type { UserStoreState } from '#auth/hooks/use-user-store.hook';
import type { AuthLoginResponseSchema } from '#auth/apis/auth.api';
import type { UserStoreState } from '#auth/hooks/use-auth-user-store.hook';
import { faker } from '@faker-js/faker';

export function seedUser(): LoginApiResponseSchema {
export function seedUser(): AuthLoginResponseSchema {
return {
id: faker.number.int(),
username: faker.person.middleName(),
Expand All @@ -24,7 +24,7 @@ export function getLocalStorageUser(): {
return JSON.parse(localStorage.getItem('app-user') ?? 'null');
}

export function setLocalStorageUser(user: LoginApiResponseSchema) {
export function setLocalStorageUser(user: AuthLoginResponseSchema) {
if (!localStorage) throw new Error('You are not in the browser env!');

localStorage.setItem('app-user', JSON.stringify(user));
Expand Down
2 changes: 1 addition & 1 deletion e2e/home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test.describe('unauthorized', () => {
const passwordInput = page.getByRole('textbox', { name: /password/i });
const submitBtn = page.getByRole('button', { name: /login/i });

await page.waitForURL('/login');
await page.waitForURL(/\/login/);
await expect(usernameInput).toBeVisible();
await expect(passwordInput).toBeVisible();
await expect(submitBtn).toBeVisible();
Expand Down
4 changes: 2 additions & 2 deletions e2e/todo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ test.describe('authorized', () => {
// we can update by clicking submit btn / press Enter in the input text
await input.fill('updated todo');
await updateBtn.click();
await page.waitForURL('/todos');
await page.waitForURL(/\/todos/);
await expect(input).not.toBeVisible();
await expect(updateBtn).not.toBeVisible();
});
Expand Down Expand Up @@ -73,7 +73,7 @@ test.describe('unauthorized', () => {
const passwordInput = page.getByRole('textbox', { name: /password/i });
const submitBtn = page.getByRole('button', { name: /login/i });

await page.waitForURL('/login');
await page.waitForURL(/\/login/);
await expect(usernameInput).toBeVisible();
await expect(passwordInput).toBeVisible();
await expect(submitBtn).toBeVisible();
Expand Down
4 changes: 2 additions & 2 deletions e2e/todos.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ test.describe('authorized', () => {
await page.goto('/todos?limit=50');

const gridItems = page.getByRole('row');
const removeBtns = page.getByRole('button', { name: 'Remove' });
const removeBtns = page.getByTestId('todo-delete');

// wait query to success
await gridItems.nth(49).waitFor({ state: 'visible' });
Expand All @@ -152,7 +152,7 @@ test.describe('unauthorized', () => {
const passwordInput = page.getByRole('textbox', { name: /password/i });
const submitBtn = page.getByRole('button', { name: /login/i });

await page.waitForURL('/login');
await page.waitForURL(/\/login/);
await expect(usernameInput).toBeVisible();
await expect(passwordInput).toBeVisible();
await expect(submitBtn).toBeVisible();
Expand Down
9 changes: 7 additions & 2 deletions src/app/devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ const ReactQueryDevtoolsProduction = React.lazy(() =>
),
);

export function Devtools() {
export function Devtools(
props: React.ComponentProps<typeof ReactQueryDevtools> = {
buttonPosition: 'bottom-right',
initialIsOpen: false,
},
) {
const [showDevtools, setShowDevtools] = React.useState(false);

React.useEffect(() => {
Expand All @@ -19,7 +24,7 @@ export function Devtools() {
return (
<>
{/* this will only be rendered in development */}
<ReactQueryDevtools buttonPosition="bottom-left" initialIsOpen={false} />
<ReactQueryDevtools {...props} />

{showDevtools && (
<React.Suspense fallback={null}>
Expand Down
17 changes: 10 additions & 7 deletions src/app/providers/i18n/context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useState } from 'react';
import React, { createContext, useState } from 'react';

export type LocaleDictLanguage = 'en-US' | 'id-ID';
export type I18nContextInterface = ReturnType<typeof useI18nContext>;
Expand All @@ -7,13 +7,16 @@ export type I18nContextInterface = ReturnType<typeof useI18nContext>;
export function useI18nContext() {
const [locale, setLocale] = useState<LocaleDictLanguage>('en-US');

const actions = {
changeLocale: (newLocale: LocaleDictLanguage) => {
setLocale(newLocale);
},
};
const actions = React.useMemo(
() => ({
changeLocale: (newLocale: LocaleDictLanguage) => {
setLocale(newLocale);
},
}),
[],
);

return [locale, actions] as const;
return React.useMemo(() => [locale, actions] as const, [locale, actions]);
}

export const I18nContext = createContext<I18nContextInterface>(
Expand Down
50 changes: 30 additions & 20 deletions src/modules/auth/apis/auth.api.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { http } from '#shared/services/http.service';
import { z } from 'zod';

export type LoginSchema = z.infer<typeof loginSchema>;
export type LoginApiResponseSchema = z.infer<typeof loginApiResponseSchema>;

// #region SCHEMAS
export const loginSchema = z.object({
// #region API SCHEMAS
export const authLoginRequestSchema = z.object({
username: z.string().min(3, 'username must contain at least 3 characters'),
password: z.string().min(6, 'password must contain at least 6 characters'),
expiresInMins: z.number().optional(),
});

export const loginApiResponseSchema = z.object({
export const authLoginResponseSchema = z.object({
id: z.number().positive(),
username: z.string(),
email: z.string().email(),
Expand All @@ -21,31 +17,45 @@ export const loginApiResponseSchema = z.object({
image: z.string().url(),
token: z.string(),
});
// #endregion
// #endregion API SCHEMAS

// #region SCHEMAS TYPES
export type AuthLoginRequestSchema = z.infer<typeof authLoginRequestSchema>;
export type AuthLoginResponseSchema = z.infer<typeof authLoginResponseSchema>;
// #endregion SCHEMAS TYPES

export const authApi = {
login: async (creds: LoginSchema) => {
export const authKeys = {
all: ['auth'] as const,
login: (params: AuthLoginRequestSchema | undefined) =>
[...authKeys.all, 'login', ...(params ? [params] : [])] as const,
};

export const authRepositories = {
/**
* @url POST ${env.apiBaseUrl}/auth/login
* @note could throw error in `HttpError` or `ZodError` error
*/
login: async ({ json }: { json: AuthLoginRequestSchema }) => {
const resp = await http
.post('auth/login', {
throwHttpErrors: false, // i'm expecting error response from the backend
json: creds,
json,
hooks: {
afterResponse: [
async (request, _options, response) => {
if (response.status === 200) {
const data = (await response.json()) as LoginApiResponseSchema;
// set 'Authorization' headers
request.headers.set('Authorization', `Bearer ${data.token}`);
const data = (await response.json()) as AuthLoginResponseSchema;

if ('token' in data) {
// set 'Authorization' headers
request.headers.set('Authorization', `Bearer ${data.token}`);
}
}
},
],
},
})
.json<LoginApiResponseSchema>();

// we also can use `parse` here. `parse` will throw if `resp.data` is not correct, and therefore can render `errorElement` if specified
// const loginApiResponse = loginApiResponseSchema.parse(resp.data);
.json<AuthLoginResponseSchema>();

return resp;
return authLoginResponseSchema.parse(resp);
},
} as const;
111 changes: 0 additions & 111 deletions src/modules/auth/components/login-form.tsx

This file was deleted.

38 changes: 38 additions & 0 deletions src/modules/auth/hooks/use-auth-checker.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useAuthUserStore } from '#auth/hooks/use-auth-user-store.hook';
import { authPath } from '#auth/routes';
import { homePath } from '#home/routes';
import { useI18n } from '#shared/hooks/use-i18n/use-i18n.hook';
import { useMount } from '#shared/hooks/use-mount.hook';
import { useLocation, useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { P, match } from 'ts-pattern';

/**
* Hooks to check the authentication of your user, wheter they're logged in or not
*
* @example
*
* ```tsx
* useAuthChecker()
* ```
*/
export function useAuthChecker() {
const [t] = useI18n();
const navigate = useNavigate();
const location = useLocation();
const { user } = useAuthUserStore();

useMount(() => {
match([!!user, location.pathname.includes('login')])
.with([false, true], () => {})
.with([false, P.any], () => {
navigate(authPath.login, { replace: true });
toast.error(t('unauthorized'));
})
.with([true, true], () => {
navigate(homePath.root);
toast.info(t('authorized'));
})
.otherwise(() => {});
});
}
Loading

0 comments on commit 7cf0fb1

Please sign in to comment.