Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: auth screens #8

Merged
merged 20 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2c28742
feat: expand e2e tests for auth.controller and add new migration file
typeWolffo Jul 15, 2024
0f2a83c
feat: update password change functionality and schema
typeWolffo Jul 15, 2024
ef4ab29
feat: create e2e test setup function
typeWolffo Jul 15, 2024
f236054
refactor: improve user and credential factories, add userWithCredenti…
typeWolffo Jul 17, 2024
c64afb8
refactor: refactor user factory and add hashPassword function
typeWolffo Jul 17, 2024
565611b
fix: update password handling in user controller e2e tests
typeWolffo Jul 17, 2024
1729fc0
feat: refactor userFactory and authService.register
typeWolffo Jul 17, 2024
95144cc
feat: create Card components and update utility usage
typeWolffo Jul 16, 2024
6a0c47f
feat: update tailwind configuration and vite aliases
typeWolffo Jul 16, 2024
fdd99bf
feat: add AuthLayout and RegisterPage components
typeWolffo Jul 16, 2024
9faae21
feat: update API and generated API for authentication
typeWolffo Jul 16, 2024
c0e368e
feat: add auth screens
typeWolffo Jul 16, 2024
985f223
--wip-- [skip ci]
typeWolffo Jul 17, 2024
fac361f
--wip-- [skip ci]
typeWolffo Jul 18, 2024
3bdb8a2
feat: add navigation menu and pattern matching
typeWolffo Jul 19, 2024
693c8ae
fix: replace hardcoded CORS origin with environment variable
typeWolffo Jul 19, 2024
58a5317
feat: create SheetMenu component and import ThemeToggle
typeWolffo Jul 19, 2024
9531003
refactor: refactor theme management using useThemeStore and useEffect
typeWolffo Jul 19, 2024
070b41a
refactor: update Toaster component and useTheme usage
typeWolffo Jul 19, 2024
49e48ee
refactor: update ThemeToggle import paths and usage
typeWolffo Jul 19, 2024
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 change: 1 addition & 0 deletions examples/common_nestjs_remix/apps/api/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
DATABASE_URL="postgres://postgres:guidebook@localhost:5432/guidebook"
JWT_SECRET=
JWT_REFRESH_SECRET=
CORS_ORIGIN=
5 changes: 5 additions & 0 deletions examples/common_nestjs_remix/apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ async function bootstrap() {

app.use(cookieParser());

app.enableCors({
origin: process.env.CORS_ORIGIN,
credentials: true,
});

const config = new DocumentBuilder()
.setTitle("Guidebook API")
.setDescription("Example usage of Swagger with Typebox")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { writeFile } from 'node:fs';
import { writeFile } from "node:fs";

const SCHEMA_FILE = './src/swagger/api-schema.json';
const SCHEMA_FILE = "./src/swagger/api-schema.json";

export const exportSchemaToFile = (schema: object) => {
const content = JSON.stringify(schema, null, 2);
Expand Down
3 changes: 3 additions & 0 deletions examples/common_nestjs_remix/apps/web/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ module.exports = {
typescript: {},
},
},
rules: {
"react/prop-types": "off",
},
},

// Typescript
Expand Down
27 changes: 26 additions & 1 deletion examples/common_nestjs_remix/apps/web/app/api/api-client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
import { useAuthStore } from "~/modules/Auth/authStore";
import { API } from "./generated-api";

export const ApiClient = new API({
baseURL: import.meta.env.API_URL,
baseURL: import.meta.env.VITE_API_URL,
secure: true,
withCredentials: true,
});

ApiClient.instance.interceptors.response.use(
(response) => response,
async (error) => {
const isLoggedIn = useAuthStore.getState().isLoggedIn;
const originalRequest = error.config;

if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
if (!isLoggedIn) return;

try {
await ApiClient.auth.authControllerRefreshTokens();

return ApiClient.instance(originalRequest);
} catch (error) {
return Promise.reject(error);
}
}

return Promise.reject(error);
}
);
210 changes: 192 additions & 18 deletions examples/common_nestjs_remix/apps/web/app/api/generated-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,91 @@
* ---------------------------------------------------------------
*/

export interface CreatePropertyBody {
name: string;
description?: string;
export interface RegisterBody {
/** @format email */
email: string;
/**
* @minLength 8
* @maxLength 64
*/
password: string;
}

export interface CreatePropertyResponse {
export interface RegisterResponse {
data: {
id: string;
createdAt: string;
updatedAt: string;
name: string;
description: string | null;
email: string;
};
}

export type DeletePropertyResponse = null;
export interface LoginBody {
/** @format email */
email: string;
/**
* @minLength 8
* @maxLength 64
*/
password: string;
}

export interface LoginResponse {
data: {
id: string;
createdAt: string;
updatedAt: string;
email: string;
};
}

export type LogoutResponse = null;

export type RefreshTokensResponse = null;

export interface GetUsersResponse {
data: {
id: string;
createdAt: string;
updatedAt: string;
email: string;
}[];
}

export interface GetUserByIdResponse {
data: {
id: string;
createdAt: string;
updatedAt: string;
email: string;
};
}

export interface UpdateUserBody {
/** @format email */
email?: string;
}

export interface UpdateUserResponse {
data: {
id: string;
createdAt: string;
updatedAt: string;
email: string;
};
}

export interface ChangePasswordBody {
/**
* @minLength 8
* @maxLength 64
*/
password: string;
}

export type ChangePasswordResponse = null;

export type DeleteUserResponse = null;

import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, HeadersDefaults, ResponseType } from "axios";
import axios from "axios";
Expand Down Expand Up @@ -168,16 +237,16 @@ export class HttpClient<SecurityDataType = unknown> {
* Example usage of Swagger with Typebox
*/
export class API<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> {
properties = {
auth = {
/**
* No description
*
* @name PropertiesControllerCreateProperty
* @request POST:/properties
* @name AuthControllerRegister
* @request POST:/auth/register
*/
propertiesControllerCreateProperty: (data: CreatePropertyBody, params: RequestParams = {}) =>
this.request<CreatePropertyResponse, any>({
path: `/properties`,
authControllerRegister: (data: RegisterBody, params: RequestParams = {}) =>
this.request<RegisterResponse, any>({
path: `/auth/register`,
method: "POST",
body: data,
type: ContentType.Json,
Expand All @@ -188,15 +257,120 @@ export class API<SecurityDataType extends unknown> extends HttpClient<SecurityDa
/**
* No description
*
* @name PropertiesControllerDeleteProperty
* @request DELETE:/properties/{id}
* @name AuthControllerLogin
* @request POST:/auth/login
*/
propertiesControllerDeleteProperty: (id: string, params: RequestParams = {}) =>
this.request<DeletePropertyResponse, any>({
path: `/properties/${id}`,
authControllerLogin: (data: LoginBody, params: RequestParams = {}) =>
this.request<LoginResponse, any>({
path: `/auth/login`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
}),

/**
* No description
*
* @name AuthControllerLogout
* @request POST:/auth/logout
*/
authControllerLogout: (params: RequestParams = {}) =>
this.request<LogoutResponse, any>({
path: `/auth/logout`,
method: "POST",
format: "json",
...params,
}),

/**
* No description
*
* @name AuthControllerRefreshTokens
* @request POST:/auth/refresh
*/
authControllerRefreshTokens: (params: RequestParams = {}) =>
this.request<RefreshTokensResponse, any>({
path: `/auth/refresh`,
method: "POST",
format: "json",
...params,
}),
};
users = {
/**
* No description
*
* @name UsersControllerGetUsers
* @request GET:/users
*/
usersControllerGetUsers: (params: RequestParams = {}) =>
this.request<GetUsersResponse, any>({
path: `/users`,
method: "GET",
format: "json",
...params,
}),

/**
* No description
*
* @name UsersControllerGetUserById
* @request GET:/users/{id}
*/
usersControllerGetUserById: (id: string, params: RequestParams = {}) =>
this.request<GetUserByIdResponse, any>({
path: `/users/${id}`,
method: "GET",
format: "json",
...params,
}),

/**
* No description
*
* @name UsersControllerUpdateUser
* @request PATCH:/users/{id}
*/
usersControllerUpdateUser: (id: string, data: UpdateUserBody, params: RequestParams = {}) =>
this.request<UpdateUserResponse, any>({
path: `/users/${id}`,
method: "PATCH",
body: data,
type: ContentType.Json,
format: "json",
...params,
}),

/**
* No description
*
* @name UsersControllerDeleteUser
* @request DELETE:/users/{id}
*/
usersControllerDeleteUser: (id: string, params: RequestParams = {}) =>
this.request<DeleteUserResponse, any>({
path: `/users/${id}`,
method: "DELETE",
format: "json",
...params,
}),

/**
* No description
*
* @name UsersControllerChangePassword
* @request PATCH:/users/{id}/change-password
*/
usersControllerChangePassword: (id: string, data: ChangePasswordBody, params: RequestParams = {}) =>
this.request<ChangePasswordResponse, any>({
path: `/users/${id}/change-password`,
method: "PATCH",
body: data,
type: ContentType.Json,
format: "json",
...params,
}),
};
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useMutation } from "@tanstack/react-query";
import { ApiClient } from "../api-client";
import { LoginBody } from "../generated-api";
import { useAuthStore } from "~/modules/Auth/authStore";
import { toast } from "sonner";
import { AxiosError } from "axios";

type LoginUserOptions = {
data: LoginBody;
};

export function useLoginUser() {
const { setLoggedIn } = useAuthStore();
return useMutation({
mutationFn: async (options: LoginUserOptions) => {
const response = await ApiClient.auth.authControllerLogin(options.data);

return response.data;
},
onSuccess: () => {
setLoggedIn(true);
},
onError: (error) => {
if (error instanceof AxiosError) {
return toast.error(error.response?.data.message);
}
toast.error(error.message);
},
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useAuthStore } from "./../../modules/Auth/authStore";
import { useMutation } from "@tanstack/react-query";
import { ApiClient } from "../api-client";
import { toast } from "sonner";
import { AxiosError } from "axios";

export function useLogoutUser() {
const { setLoggedIn } = useAuthStore();
return useMutation({
mutationFn: async () => {
const response = await ApiClient.auth.authControllerLogout();

return response.data;
},
onSuccess: () => {
setLoggedIn(false);
},
onError: (error) => {
if (error instanceof AxiosError) {
return toast.error(error.response?.data.message);
}
toast.error(error.message);
},
});
}
Loading