Browse Source

♻️ Refactor and tweaks, rename `UserCreateOpen` to `UserRegister` and others (#1143)

pull/13907/head
Alejandra 1 year ago
committed by GitHub
parent
commit
4239d93ea6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 8
      backend/app/api/routes/users.py
  2. 2
      backend/app/models.py
  3. 12
      backend/app/tests/api/routes/test_users.py
  4. 4
      frontend/src/client/index.ts
  5. 2
      frontend/src/client/models/UserRegister.ts
  6. 60
      frontend/src/client/schemas/$Body_login_login_access_token.ts
  7. 10
      frontend/src/client/schemas/$HTTPValidationError.ts
  8. 20
      frontend/src/client/schemas/$ItemCreate.ts
  9. 32
      frontend/src/client/schemas/$ItemOut.ts
  10. 28
      frontend/src/client/schemas/$ItemUpdate.ts
  11. 18
      frontend/src/client/schemas/$ItemsOut.ts
  12. 6
      frontend/src/client/schemas/$Message.ts
  13. 12
      frontend/src/client/schemas/$NewPassword.ts
  14. 10
      frontend/src/client/schemas/$Token.ts
  15. 12
      frontend/src/client/schemas/$UpdatePassword.ts
  16. 34
      frontend/src/client/schemas/$UserCreate.ts
  17. 24
      frontend/src/client/schemas/$UserCreateOpen.ts
  18. 34
      frontend/src/client/schemas/$UserOut.ts
  19. 24
      frontend/src/client/schemas/$UserRegister.ts
  20. 50
      frontend/src/client/schemas/$UserUpdate.ts
  21. 28
      frontend/src/client/schemas/$UserUpdateMe.ts
  22. 18
      frontend/src/client/schemas/$UsersOut.ts
  23. 34
      frontend/src/client/schemas/$ValidationError.ts
  24. 48
      frontend/src/client/services/ItemsService.ts
  25. 47
      frontend/src/client/services/LoginService.ts
  26. 80
      frontend/src/client/services/UsersService.ts
  27. 30
      frontend/src/client/services/UtilsService.ts
  28. 33
      frontend/src/components/Admin/AddUser.tsx
  29. 32
      frontend/src/components/Admin/EditUser.tsx
  30. 2
      frontend/src/components/Common/Navbar.tsx
  31. 4
      frontend/src/components/Common/Sidebar.tsx
  32. 12
      frontend/src/components/Common/SidebarItems.tsx
  33. 2
      frontend/src/components/Common/UserMenu.tsx
  34. 33
      frontend/src/components/Items/AddItem.tsx
  35. 34
      frontend/src/components/Items/EditItem.tsx
  36. 44
      frontend/src/components/UserSettings/ChangePassword.tsx
  37. 41
      frontend/src/components/UserSettings/DeleteConfirmation.tsx
  38. 35
      frontend/src/components/UserSettings/UserInformation.tsx
  39. 24
      frontend/src/hooks/useAuth.ts
  40. 2
      frontend/src/routes/_layout/admin.tsx
  41. 2
      frontend/src/routes/_layout/items.tsx
  42. 14
      frontend/src/routes/login.tsx
  43. 16
      frontend/src/routes/reset-password.tsx
  44. 7
      frontend/src/theme.tsx
  45. 31
      frontend/src/utils.ts

8
backend/app/api/routes/users.py

@ -17,8 +17,8 @@ from app.models import (
UpdatePassword,
User,
UserCreate,
UserCreateOpen,
UserOut,
UserRegister,
UsersOut,
UserUpdate,
UserUpdateMe,
@ -122,8 +122,8 @@ def read_user_me(session: SessionDep, current_user: CurrentUser) -> Any:
return current_user
@router.post("/open", response_model=UserOut)
def create_user_open(session: SessionDep, user_in: UserCreateOpen) -> Any:
@router.post("/signup", response_model=UserOut)
def register_user(session: SessionDep, user_in: UserRegister) -> Any:
"""
Create new user without the need to be logged in.
"""
@ -138,7 +138,7 @@ def create_user_open(session: SessionDep, user_in: UserCreateOpen) -> Any:
status_code=400,
detail="The user with this email already exists in the system",
)
user_create = UserCreate.from_orm(user_in)
user_create = UserCreate.model_validate(user_in)
user = crud.create_user(session=session, user_create=user_create)
return user

2
backend/app/models.py

@ -16,7 +16,7 @@ class UserCreate(UserBase):
# TODO replace email str with EmailStr when sqlmodel supports it
class UserCreateOpen(SQLModel):
class UserRegister(SQLModel):
email: str
password: str
full_name: str | None = None

12
backend/app/tests/api/routes/test_users.py

@ -263,14 +263,14 @@ def test_update_password_me_same_password_error(
)
def test_create_user_open(client: TestClient) -> None:
def test_register_user(client: TestClient) -> None:
with patch("app.core.config.settings.USERS_OPEN_REGISTRATION", True):
username = random_email()
password = random_lower_string()
full_name = random_lower_string()
data = {"email": username, "password": password, "full_name": full_name}
r = client.post(
f"{settings.API_V1_STR}/users/open",
f"{settings.API_V1_STR}/users/signup",
json=data,
)
assert r.status_code == 200
@ -279,14 +279,14 @@ def test_create_user_open(client: TestClient) -> None:
assert created_user["full_name"] == full_name
def test_create_user_open_forbidden_error(client: TestClient) -> None:
def test_register_user_forbidden_error(client: TestClient) -> None:
with patch("app.core.config.settings.USERS_OPEN_REGISTRATION", False):
username = random_email()
password = random_lower_string()
full_name = random_lower_string()
data = {"email": username, "password": password, "full_name": full_name}
r = client.post(
f"{settings.API_V1_STR}/users/open",
f"{settings.API_V1_STR}/users/signup",
json=data,
)
assert r.status_code == 403
@ -295,7 +295,7 @@ def test_create_user_open_forbidden_error(client: TestClient) -> None:
)
def test_create_user_open_already_exists_error(client: TestClient) -> None:
def test_register_user_already_exists_error(client: TestClient) -> None:
with patch("app.core.config.settings.USERS_OPEN_REGISTRATION", True):
password = random_lower_string()
full_name = random_lower_string()
@ -305,7 +305,7 @@ def test_create_user_open_already_exists_error(client: TestClient) -> None:
"full_name": full_name,
}
r = client.post(
f"{settings.API_V1_STR}/users/open",
f"{settings.API_V1_STR}/users/signup",
json=data,
)
assert r.status_code == 400

4
frontend/src/client/index.ts

@ -18,8 +18,8 @@ export type { NewPassword } from './models/NewPassword';
export type { Token } from './models/Token';
export type { UpdatePassword } from './models/UpdatePassword';
export type { UserCreate } from './models/UserCreate';
export type { UserCreateOpen } from './models/UserCreateOpen';
export type { UserOut } from './models/UserOut';
export type { UserRegister } from './models/UserRegister';
export type { UsersOut } from './models/UsersOut';
export type { UserUpdate } from './models/UserUpdate';
export type { UserUpdateMe } from './models/UserUpdateMe';
@ -36,8 +36,8 @@ export { $NewPassword } from './schemas/$NewPassword';
export { $Token } from './schemas/$Token';
export { $UpdatePassword } from './schemas/$UpdatePassword';
export { $UserCreate } from './schemas/$UserCreate';
export { $UserCreateOpen } from './schemas/$UserCreateOpen';
export { $UserOut } from './schemas/$UserOut';
export { $UserRegister } from './schemas/$UserRegister';
export { $UsersOut } from './schemas/$UsersOut';
export { $UserUpdate } from './schemas/$UserUpdate';
export { $UserUpdateMe } from './schemas/$UserUpdateMe';

2
frontend/src/client/models/UserCreateOpen.ts → frontend/src/client/models/UserRegister.ts

@ -3,7 +3,7 @@
/* tslint:disable */
/* eslint-disable */
export type UserCreateOpen = {
export type UserRegister = {
email: string;
password: string;
full_name?: (string | null);

60
frontend/src/client/schemas/$Body_login_login_access_token.ts

@ -5,40 +5,40 @@
export const $Body_login_login_access_token = {
properties: {
grant_type: {
type: 'any-of',
contains: [{
type: 'string',
pattern: 'password',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
pattern: 'password',
}, {
type: 'null',
}],
},
username: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
password: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
scope: {
type: 'string',
},
type: 'string',
},
client_id: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
client_secret: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
},
} as const;

10
frontend/src/client/schemas/$HTTPValidationError.ts

@ -5,10 +5,10 @@
export const $HTTPValidationError = {
properties: {
detail: {
type: 'array',
contains: {
type: 'ValidationError',
},
},
type: 'array',
contains: {
type: 'ValidationError',
},
},
},
} as const;

20
frontend/src/client/schemas/$ItemCreate.ts

@ -5,16 +5,16 @@
export const $ItemCreate = {
properties: {
title: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
description: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
},
} as const;

32
frontend/src/client/schemas/$ItemOut.ts

@ -5,24 +5,24 @@
export const $ItemOut = {
properties: {
title: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
description: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
id: {
type: 'number',
isRequired: true,
},
type: 'number',
isRequired: true,
},
owner_id: {
type: 'number',
isRequired: true,
},
type: 'number',
isRequired: true,
},
},
} as const;

28
frontend/src/client/schemas/$ItemUpdate.ts

@ -5,20 +5,20 @@
export const $ItemUpdate = {
properties: {
title: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
description: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
},
} as const;

18
frontend/src/client/schemas/$ItemsOut.ts

@ -5,15 +5,15 @@
export const $ItemsOut = {
properties: {
data: {
type: 'array',
contains: {
type: 'ItemOut',
},
isRequired: true,
},
type: 'array',
contains: {
type: 'ItemOut',
},
isRequired: true,
},
count: {
type: 'number',
isRequired: true,
},
type: 'number',
isRequired: true,
},
},
} as const;

6
frontend/src/client/schemas/$Message.ts

@ -5,8 +5,8 @@
export const $Message = {
properties: {
message: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
},
} as const;

12
frontend/src/client/schemas/$NewPassword.ts

@ -5,12 +5,12 @@
export const $NewPassword = {
properties: {
token: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
new_password: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
},
} as const;

10
frontend/src/client/schemas/$Token.ts

@ -5,11 +5,11 @@
export const $Token = {
properties: {
access_token: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
token_type: {
type: 'string',
},
type: 'string',
},
},
} as const;

12
frontend/src/client/schemas/$UpdatePassword.ts

@ -5,12 +5,12 @@
export const $UpdatePassword = {
properties: {
current_password: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
new_password: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
},
} as const;

34
frontend/src/client/schemas/$UserCreate.ts

@ -5,26 +5,26 @@
export const $UserCreate = {
properties: {
email: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
is_active: {
type: 'boolean',
},
type: 'boolean',
},
is_superuser: {
type: 'boolean',
},
type: 'boolean',
},
full_name: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
password: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
},
} as const;

24
frontend/src/client/schemas/$UserCreateOpen.ts

@ -1,24 +0,0 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $UserCreateOpen = {
properties: {
email: {
type: 'string',
isRequired: true,
},
password: {
type: 'string',
isRequired: true,
},
full_name: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
},
} as const;

34
frontend/src/client/schemas/$UserOut.ts

@ -5,26 +5,26 @@
export const $UserOut = {
properties: {
email: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
is_active: {
type: 'boolean',
},
type: 'boolean',
},
is_superuser: {
type: 'boolean',
},
type: 'boolean',
},
full_name: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
id: {
type: 'number',
isRequired: true,
},
type: 'number',
isRequired: true,
},
},
} as const;

24
frontend/src/client/schemas/$UserRegister.ts

@ -0,0 +1,24 @@
/* generated using openapi-typescript-codegen -- do no edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export const $UserRegister = {
properties: {
email: {
type: 'string',
isRequired: true,
},
password: {
type: 'string',
isRequired: true,
},
full_name: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
},
} as const;

50
frontend/src/client/schemas/$UserUpdate.ts

@ -5,34 +5,34 @@
export const $UserUpdate = {
properties: {
email: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
is_active: {
type: 'boolean',
},
type: 'boolean',
},
is_superuser: {
type: 'boolean',
},
type: 'boolean',
},
full_name: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
password: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
},
} as const;

28
frontend/src/client/schemas/$UserUpdateMe.ts

@ -5,20 +5,20 @@
export const $UserUpdateMe = {
properties: {
full_name: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
email: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'null',
}],
},
},
} as const;

18
frontend/src/client/schemas/$UsersOut.ts

@ -5,15 +5,15 @@
export const $UsersOut = {
properties: {
data: {
type: 'array',
contains: {
type: 'UserOut',
},
isRequired: true,
},
type: 'array',
contains: {
type: 'UserOut',
},
isRequired: true,
},
count: {
type: 'number',
isRequired: true,
},
type: 'number',
isRequired: true,
},
},
} as const;

34
frontend/src/client/schemas/$ValidationError.ts

@ -5,24 +5,24 @@
export const $ValidationError = {
properties: {
loc: {
type: 'array',
contains: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'number',
}],
},
isRequired: true,
},
type: 'array',
contains: {
type: 'any-of',
contains: [{
type: 'string',
}, {
type: 'number',
}],
},
isRequired: true,
},
msg: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
type: {
type: 'string',
isRequired: true,
},
type: 'string',
isRequired: true,
},
},
} as const;

48
frontend/src/client/services/ItemsService.ts

@ -21,12 +21,12 @@ export class ItemsService {
* @throws ApiError
*/
public static readItems({
skip,
limit = 100,
}: {
skip?: number,
limit?: number,
}): CancelablePromise<ItemsOut> {
skip,
limit = 100,
}: {
skip?: number,
limit?: number,
}): CancelablePromise<ItemsOut> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/items/',
@ -47,10 +47,10 @@ limit?: number,
* @throws ApiError
*/
public static createItem({
requestBody,
}: {
requestBody: ItemCreate,
}): CancelablePromise<ItemOut> {
requestBody,
}: {
requestBody: ItemCreate,
}): CancelablePromise<ItemOut> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/items/',
@ -69,10 +69,10 @@ requestBody: ItemCreate,
* @throws ApiError
*/
public static readItem({
id,
}: {
id: number,
}): CancelablePromise<ItemOut> {
id,
}: {
id: number,
}): CancelablePromise<ItemOut> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/items/{id}',
@ -92,12 +92,12 @@ id: number,
* @throws ApiError
*/
public static updateItem({
id,
requestBody,
}: {
id: number,
requestBody: ItemUpdate,
}): CancelablePromise<ItemOut> {
id,
requestBody,
}: {
id: number,
requestBody: ItemUpdate,
}): CancelablePromise<ItemOut> {
return __request(OpenAPI, {
method: 'PUT',
url: '/api/v1/items/{id}',
@ -119,10 +119,10 @@ requestBody: ItemUpdate,
* @throws ApiError
*/
public static deleteItem({
id,
}: {
id: number,
}): CancelablePromise<Message> {
id,
}: {
id: number,
}): CancelablePromise<Message> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/api/v1/items/{id}',

47
frontend/src/client/services/LoginService.ts

@ -21,10 +21,10 @@ export class LoginService {
* @throws ApiError
*/
public static loginAccessToken({
formData,
}: {
formData: Body_login_login_access_token,
}): CancelablePromise<Token> {
formData,
}: {
formData: Body_login_login_access_token,
}): CancelablePromise<Token> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/login/access-token',
@ -56,10 +56,10 @@ formData: Body_login_login_access_token,
* @throws ApiError
*/
public static recoverPassword({
email,
}: {
email: string,
}): CancelablePromise<Message> {
email,
}: {
email: string,
}): CancelablePromise<Message> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/password-recovery/{email}',
@ -79,10 +79,10 @@ email: string,
* @throws ApiError
*/
public static resetPassword({
requestBody,
}: {
requestBody: NewPassword,
}): CancelablePromise<Message> {
requestBody,
}: {
requestBody: NewPassword,
}): CancelablePromise<Message> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/reset-password/',
@ -94,4 +94,27 @@ requestBody: NewPassword,
});
}
/**
* Recover Password Html Content
* HTML Content for Password Recovery
* @returns string Successful Response
* @throws ApiError
*/
public static recoverPasswordHtmlContent({
email,
}: {
email: string,
}): CancelablePromise<string> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/password-recovery-html-content/{email}',
path: {
'email': email,
},
errors: {
422: `Validation Error`,
},
});
}
}

80
frontend/src/client/services/UsersService.ts

@ -5,8 +5,8 @@
import type { Message } from '../models/Message';
import type { UpdatePassword } from '../models/UpdatePassword';
import type { UserCreate } from '../models/UserCreate';
import type { UserCreateOpen } from '../models/UserCreateOpen';
import type { UserOut } from '../models/UserOut';
import type { UserRegister } from '../models/UserRegister';
import type { UsersOut } from '../models/UsersOut';
import type { UserUpdate } from '../models/UserUpdate';
import type { UserUpdateMe } from '../models/UserUpdateMe';
@ -24,12 +24,12 @@ export class UsersService {
* @throws ApiError
*/
public static readUsers({
skip,
limit = 100,
}: {
skip?: number,
limit?: number,
}): CancelablePromise<UsersOut> {
skip,
limit = 100,
}: {
skip?: number,
limit?: number,
}): CancelablePromise<UsersOut> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/users/',
@ -50,10 +50,10 @@ limit?: number,
* @throws ApiError
*/
public static createUser({
requestBody,
}: {
requestBody: UserCreate,
}): CancelablePromise<UserOut> {
requestBody,
}: {
requestBody: UserCreate,
}): CancelablePromise<UserOut> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/users/',
@ -85,10 +85,10 @@ requestBody: UserCreate,
* @throws ApiError
*/
public static updateUserMe({
requestBody,
}: {
requestBody: UserUpdateMe,
}): CancelablePromise<UserOut> {
requestBody,
}: {
requestBody: UserUpdateMe,
}): CancelablePromise<UserOut> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/api/v1/users/me',
@ -107,10 +107,10 @@ requestBody: UserUpdateMe,
* @throws ApiError
*/
public static updatePasswordMe({
requestBody,
}: {
requestBody: UpdatePassword,
}): CancelablePromise<Message> {
requestBody,
}: {
requestBody: UpdatePassword,
}): CancelablePromise<Message> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/api/v1/users/me/password',
@ -123,19 +123,19 @@ requestBody: UpdatePassword,
}
/**
* Create User Open
* Register User
* Create new user without the need to be logged in.
* @returns UserOut Successful Response
* @throws ApiError
*/
public static createUserOpen({
requestBody,
}: {
requestBody: UserCreateOpen,
}): CancelablePromise<UserOut> {
public static registerUser({
requestBody,
}: {
requestBody: UserRegister,
}): CancelablePromise<UserOut> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/users/open',
url: '/api/v1/users/signup',
body: requestBody,
mediaType: 'application/json',
errors: {
@ -151,10 +151,10 @@ requestBody: UserCreateOpen,
* @throws ApiError
*/
public static readUserById({
userId,
}: {
userId: number,
}): CancelablePromise<UserOut> {
userId,
}: {
userId: number,
}): CancelablePromise<UserOut> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/users/{user_id}',
@ -174,12 +174,12 @@ userId: number,
* @throws ApiError
*/
public static updateUser({
userId,
requestBody,
}: {
userId: number,
requestBody: UserUpdate,
}): CancelablePromise<UserOut> {
userId,
requestBody,
}: {
userId: number,
requestBody: UserUpdate,
}): CancelablePromise<UserOut> {
return __request(OpenAPI, {
method: 'PATCH',
url: '/api/v1/users/{user_id}',
@ -201,10 +201,10 @@ requestBody: UserUpdate,
* @throws ApiError
*/
public static deleteUser({
userId,
}: {
userId: number,
}): CancelablePromise<Message> {
userId,
}: {
userId: number,
}): CancelablePromise<Message> {
return __request(OpenAPI, {
method: 'DELETE',
url: '/api/v1/users/{user_id}',

30
frontend/src/client/services/UtilsService.ts

@ -10,28 +10,6 @@ import { request as __request } from '../core/request';
export class UtilsService {
/**
* Test Celery
* Test Celery worker.
* @returns Message Successful Response
* @throws ApiError
*/
public static testCelery({
requestBody,
}: {
requestBody: Message,
}): CancelablePromise<Message> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/utils/test-celery/',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Validation Error`,
},
});
}
/**
* Test Email
* Test emails.
@ -39,10 +17,10 @@ requestBody: Message,
* @throws ApiError
*/
public static testEmail({
emailTo,
}: {
emailTo: string,
}): CancelablePromise<Message> {
emailTo,
}: {
emailTo: string,
}): CancelablePromise<Message> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/utils/test-email/',

33
frontend/src/components/Admin/AddUser.tsx

@ -53,24 +53,23 @@ const AddUser = ({ isOpen, onClose }: AddUserProps) => {
},
})
const addUser = async (data: UserCreate) => {
await UsersService.createUser({ requestBody: data })
}
const mutation = useMutation(addUser, {
onSuccess: () => {
showToast("Success!", "User created successfully.", "success")
reset()
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
const mutation = useMutation(
(data: UserCreate) => UsersService.createUser({ requestBody: data }),
{
onSuccess: () => {
showToast("Success!", "User created successfully.", "success")
reset()
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
},
onSettled: () => {
queryClient.invalidateQueries("users")
},
},
onSettled: () => {
queryClient.invalidateQueries("users")
},
})
)
const onSubmit: SubmitHandler<UserCreateForm> = (data) => {
mutation.mutate(data)

32
frontend/src/components/Admin/EditUser.tsx

@ -52,23 +52,23 @@ const EditUser = ({ user, isOpen, onClose }: EditUserProps) => {
defaultValues: user,
})
const updateUser = async (data: UserUpdateForm) => {
await UsersService.updateUser({ userId: user.id, requestBody: data })
}
const mutation = useMutation(updateUser, {
onSuccess: () => {
showToast("Success!", "User updated successfully.", "success")
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
const mutation = useMutation(
(data: UserUpdateForm) =>
UsersService.updateUser({ userId: user.id, requestBody: data }),
{
onSuccess: () => {
showToast("Success!", "User updated successfully.", "success")
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
},
onSettled: () => {
queryClient.invalidateQueries("users")
},
},
onSettled: () => {
queryClient.invalidateQueries("users")
},
})
)
const onSubmit: SubmitHandler<UserUpdateForm> = async (data) => {
if (data.password === "") {

2
frontend/src/components/Common/Navbar.tsx

@ -18,7 +18,7 @@ const Navbar = ({ type }: NavbarProps) => {
{/* TODO: Complete search functionality */}
{/* <InputGroup w={{ base: '100%', md: 'auto' }}>
<InputLeftElement pointerEvents='none'>
<Icon as={FaSearch} color='gray.400' />
<Icon as={FaSearch} color='ui.dim' />
</InputLeftElement>
<Input type='text' placeholder='Search' fontSize={{ base: 'sm', md: 'inherit' }} borderRadius='8px' />
</InputGroup> */}

4
frontend/src/components/Common/Sidebar.tsx

@ -22,8 +22,8 @@ import SidebarItems from "./SidebarItems"
const Sidebar = () => {
const queryClient = useQueryClient()
const bgColor = useColorModeValue("ui.white", "ui.dark")
const textColor = useColorModeValue("ui.dark", "ui.white")
const bgColor = useColorModeValue("ui.light", "ui.dark")
const textColor = useColorModeValue("ui.dark", "ui.light")
const secBgColor = useColorModeValue("ui.secondary", "ui.darkSlate")
const currentUser = queryClient.getQueryData<UserOut>("currentUser")
const { isOpen, onOpen, onClose } = useDisclosure()

12
frontend/src/components/Common/SidebarItems.tsx

@ -17,7 +17,7 @@ interface SidebarItemsProps {
const SidebarItems = ({ onClose }: SidebarItemsProps) => {
const queryClient = useQueryClient()
const textColor = useColorModeValue("ui.main", "ui.white")
const textColor = useColorModeValue("ui.main", "ui.light")
const bgActive = useColorModeValue("#E2E8F0", "#4A5568")
const currentUser = queryClient.getQueryData<UserOut>("currentUser")
@ -25,13 +25,13 @@ const SidebarItems = ({ onClose }: SidebarItemsProps) => {
? [...items, { icon: FiUsers, title: "Admin", path: "/admin" }]
: items
const listItems = finalItems.map((item) => (
const listItems = finalItems.map(({ icon, title, path }) => (
<Flex
as={Link}
to={item.path}
to={path}
w="100%"
p={2}
key={item.title}
key={title}
activeProps={{
style: {
background: bgActive,
@ -41,8 +41,8 @@ const SidebarItems = ({ onClose }: SidebarItemsProps) => {
color={textColor}
onClick={onClose}
>
<Icon as={item.icon} alignSelf="center" />
<Text ml={2}>{item.title}</Text>
<Icon as={icon} alignSelf="center" />
<Text ml={2}>{title}</Text>
</Flex>
))

2
frontend/src/components/Common/UserMenu.tsx

@ -6,10 +6,10 @@ import {
MenuItem,
MenuList,
} from "@chakra-ui/react"
import { Link } from "@tanstack/react-router"
import { FaUserAstronaut } from "react-icons/fa"
import { FiLogOut, FiUser } from "react-icons/fi"
import { Link } from "@tanstack/react-router"
import useAuth from "../../hooks/useAuth"
const UserMenu = () => {

33
frontend/src/components/Items/AddItem.tsx

@ -40,24 +40,23 @@ const AddItem = ({ isOpen, onClose }: AddItemProps) => {
},
})
const addItem = async (data: ItemCreate) => {
await ItemsService.createItem({ requestBody: data })
}
const mutation = useMutation(addItem, {
onSuccess: () => {
showToast("Success!", "Item created successfully.", "success")
reset()
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
const mutation = useMutation(
(data: ItemCreate) => ItemsService.createItem({ requestBody: data }),
{
onSuccess: () => {
showToast("Success!", "Item created successfully.", "success")
reset()
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
},
onSettled: () => {
queryClient.invalidateQueries("items")
},
},
onSettled: () => {
queryClient.invalidateQueries("items")
},
})
)
const onSubmit: SubmitHandler<ItemCreate> = (data) => {
mutation.mutate(data)

34
frontend/src/components/Items/EditItem.tsx

@ -13,8 +13,8 @@ import {
ModalOverlay,
} from "@chakra-ui/react"
import { type SubmitHandler, useForm } from "react-hook-form"
import { useMutation, useQueryClient } from "react-query"
import {
type ApiError,
type ItemOut,
@ -43,23 +43,23 @@ const EditItem = ({ item, isOpen, onClose }: EditItemProps) => {
defaultValues: item,
})
const updateItem = async (data: ItemUpdate) => {
await ItemsService.updateItem({ id: item.id, requestBody: data })
}
const mutation = useMutation(updateItem, {
onSuccess: () => {
showToast("Success!", "Item updated successfully.", "success")
onClose()
const mutation = useMutation(
(data: ItemUpdate) =>
ItemsService.updateItem({ id: item.id, requestBody: data }),
{
onSuccess: () => {
showToast("Success!", "Item updated successfully.", "success")
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
},
onSettled: () => {
queryClient.invalidateQueries("items")
},
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
},
onSettled: () => {
queryClient.invalidateQueries("items")
},
})
)
const onSubmit: SubmitHandler<ItemUpdate> = async (data) => {
mutation.mutate(data)

44
frontend/src/components/UserSettings/ChangePassword.tsx

@ -14,13 +14,14 @@ import { useMutation } from "react-query"
import { type ApiError, type UpdatePassword, UsersService } from "../../client"
import useCustomToast from "../../hooks/useCustomToast"
import { confirmPasswordRules, passwordRules } from "../../utils"
interface UpdatePasswordForm extends UpdatePassword {
confirm_password: string
}
const ChangePassword = () => {
const color = useColorModeValue("inherit", "ui.white")
const color = useColorModeValue("inherit", "ui.light")
const showToast = useCustomToast()
const {
register,
@ -33,20 +34,20 @@ const ChangePassword = () => {
criteriaMode: "all",
})
const UpdatePassword = async (data: UpdatePassword) => {
await UsersService.updatePasswordMe({ requestBody: data })
}
const mutation = useMutation(UpdatePassword, {
onSuccess: () => {
showToast("Success!", "Password updated.", "success")
reset()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
const mutation = useMutation(
(data: UpdatePassword) =>
UsersService.updatePasswordMe({ requestBody: data }),
{
onSuccess: () => {
showToast("Success!", "Password updated.", "success")
reset()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
},
},
})
)
const onSubmit: SubmitHandler<UpdatePasswordForm> = async (data) => {
mutation.mutate(data)
@ -79,13 +80,7 @@ const ChangePassword = () => {
<FormLabel htmlFor="password">Set Password</FormLabel>
<Input
id="password"
{...register("new_password", {
required: "Password is required",
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
})}
{...register("new_password", passwordRules())}
placeholder="Password"
type="password"
/>
@ -97,12 +92,7 @@ const ChangePassword = () => {
<FormLabel htmlFor="confirm_password">Confirm Password</FormLabel>
<Input
id="confirm_password"
{...register("confirm_password", {
required: "Please confirm your password",
validate: (value) =>
value === getValues().new_password ||
"The passwords do not match",
})}
{...register("confirm_password", confirmPasswordRules(getValues))}
placeholder="Password"
type="password"
/>

41
frontend/src/components/UserSettings/DeleteConfirmation.tsx

@ -31,28 +31,27 @@ const DeleteConfirmation = ({ isOpen, onClose }: DeleteProps) => {
const currentUser = queryClient.getQueryData<UserOut>("currentUser")
const { logout } = useAuth()
const deleteCurrentUser = async (id: number) => {
await UsersService.deleteUser({ userId: id })
}
const mutation = useMutation(deleteCurrentUser, {
onSuccess: () => {
showToast(
"Success",
"Your account has been successfully deleted.",
"success",
)
logout()
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
const mutation = useMutation(
(id: number) => UsersService.deleteUser({ userId: id }),
{
onSuccess: () => {
showToast(
"Success",
"Your account has been successfully deleted.",
"success",
)
logout()
onClose()
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
},
onSettled: () => {
queryClient.invalidateQueries("currentUser")
},
},
onSettled: () => {
queryClient.invalidateQueries("currentUser")
},
})
)
const onSubmit = async () => {
mutation.mutate(currentUser!.id)

35
frontend/src/components/UserSettings/UserInformation.tsx

@ -27,7 +27,7 @@ import { emailPattern } from "../../utils"
const UserInformation = () => {
const queryClient = useQueryClient()
const color = useColorModeValue("inherit", "ui.white")
const color = useColorModeValue("inherit", "ui.light")
const showToast = useCustomToast()
const [editMode, setEditMode] = useState(false)
const { user: currentUser } = useAuth()
@ -50,23 +50,22 @@ const UserInformation = () => {
setEditMode(!editMode)
}
const updateInfo = async (data: UserUpdateMe) => {
await UsersService.updateUserMe({ requestBody: data })
}
const mutation = useMutation(updateInfo, {
onSuccess: () => {
showToast("Success!", "User updated successfully.", "success")
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
const mutation = useMutation(
(data: UserUpdateMe) => UsersService.updateUserMe({ requestBody: data }),
{
onSuccess: () => {
showToast("Success!", "User updated successfully.", "success")
},
onError: (err: ApiError) => {
const errDetail = err.body?.detail
showToast("Something went wrong.", `${errDetail}`, "error")
},
onSettled: () => {
queryClient.invalidateQueries("users")
queryClient.invalidateQueries("currentUser")
},
},
onSettled: () => {
queryClient.invalidateQueries("users")
queryClient.invalidateQueries("currentUser")
},
})
)
const onSubmit: SubmitHandler<UserUpdateMe> = async (data) => {
mutation.mutate(data)
@ -99,7 +98,7 @@ const UserInformation = () => {
<Text
size="md"
py={2}
color={!currentUser?.full_name ? "gray.400" : "inherit"}
color={!currentUser?.full_name ? "ui.dim" : "inherit"}
>
{currentUser?.full_name || "N/A"}
</Text>

24
frontend/src/hooks/useAuth.ts

@ -1,8 +1,10 @@
import { useNavigate } from "@tanstack/react-router"
import { useQuery } from "react-query"
import { useState } from "react"
import { useMutation, useQuery } from "react-query"
import {
type Body_login_login_access_token as AccessToken,
type ApiError,
LoginService,
type UserOut,
UsersService,
@ -13,6 +15,7 @@ const isLoggedIn = () => {
}
const useAuth = () => {
const [error, setError] = useState<string | null>(null)
const navigate = useNavigate()
const { data: user, isLoading } = useQuery<UserOut | null, Error>(
"currentUser",
@ -27,15 +30,30 @@ const useAuth = () => {
formData: data,
})
localStorage.setItem("access_token", response.access_token)
navigate({ to: "/" })
}
const loginMutation = useMutation(login, {
onSuccess: () => {
navigate({ to: "/" })
},
onError: (err: ApiError) => {
const errDetail = err.body.detail
setError(errDetail)
},
})
const logout = () => {
localStorage.removeItem("access_token")
navigate({ to: "/login" })
}
return { login, logout, user, isLoading }
return {
loginMutation,
logout,
user,
isLoading,
error,
}
}
export { isLoggedIn }

2
frontend/src/routes/_layout/admin.tsx

@ -73,7 +73,7 @@ function Admin() {
<Tbody>
{users.data.map((user) => (
<Tr key={user.id}>
<Td color={!user.full_name ? "gray.400" : "inherit"}>
<Td color={!user.full_name ? "ui.dim" : "inherit"}>
{user.full_name || "N/A"}
{currentUser?.id === user.id && (
<Badge ml="1" colorScheme="teal">

2
frontend/src/routes/_layout/items.tsx

@ -70,7 +70,7 @@ function Items() {
<Tr key={item.id}>
<Td>{item.id}</Td>
<Td>{item.title}</Td>
<Td color={!item.description ? "gray.400" : "inherit"}>
<Td color={!item.description ? "ui.dim" : "inherit"}>
{item.description || "N/A"}
</Td>
<Td>

14
frontend/src/routes/login.tsx

@ -18,11 +18,9 @@ import {
createFileRoute,
redirect,
} from "@tanstack/react-router"
import React from "react"
import { type SubmitHandler, useForm } from "react-hook-form"
import Logo from "../assets/images/fastapi-logo.svg"
import type { ApiError } from "../client"
import type { Body_login_login_access_token as AccessToken } from "../client/models/Body_login_login_access_token"
import useAuth, { isLoggedIn } from "../hooks/useAuth"
import { emailPattern } from "../utils"
@ -40,8 +38,7 @@ export const Route = createFileRoute("/login")({
function Login() {
const [show, setShow] = useBoolean()
const { login } = useAuth()
const [error, setError] = React.useState<string | null>(null)
const { loginMutation, error } = useAuth()
const {
register,
handleSubmit,
@ -56,12 +53,7 @@ function Login() {
})
const onSubmit: SubmitHandler<AccessToken> = async (data) => {
try {
await login(data)
} catch (err) {
const errDetail = (err as ApiError).body.detail
setError(errDetail)
}
loginMutation.mutate(data)
}
return (
@ -105,7 +97,7 @@ function Login() {
placeholder="Password"
/>
<InputRightElement
color="gray.400"
color="ui.dim"
_hover={{
cursor: "pointer",
}}

16
frontend/src/routes/reset-password.tsx

@ -15,6 +15,7 @@ import { useMutation } from "react-query"
import { type ApiError, LoginService, type NewPassword } from "../client"
import { isLoggedIn } from "../hooks/useAuth"
import useCustomToast from "../hooks/useCustomToast"
import { confirmPasswordRules, passwordRules } from "../utils"
interface NewPasswordForm extends NewPassword {
confirm_password: string
@ -93,13 +94,7 @@ function ResetPassword() {
<FormLabel htmlFor="password">Set Password</FormLabel>
<Input
id="password"
{...register("new_password", {
required: "Password is required",
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
})}
{...register("new_password", passwordRules())}
placeholder="Password"
type="password"
/>
@ -111,12 +106,7 @@ function ResetPassword() {
<FormLabel htmlFor="confirm_password">Confirm Password</FormLabel>
<Input
id="confirm_password"
{...register("confirm_password", {
required: "Please confirm your password",
validate: (value) =>
value === getValues().new_password ||
"The passwords do not match",
})}
{...register("confirm_password", confirmPasswordRules(getValues))}
placeholder="Password"
type="password"
/>

7
frontend/src/theme.tsx

@ -13,9 +13,10 @@ const theme = extendTheme({
secondary: "#EDF2F7",
success: "#48BB78",
danger: "#E53E3E",
white: "#FFFFFF",
light: "#FAFAFA",
dark: "#1A202C",
darkSlate: "#252D3D",
dim: "#A0AEC0",
},
},
components: {
@ -23,7 +24,7 @@ const theme = extendTheme({
variants: {
primary: {
backgroundColor: "ui.main",
color: "ui.white",
color: "ui.light",
_hover: {
backgroundColor: "#00766C",
},
@ -36,7 +37,7 @@ const theme = extendTheme({
},
danger: {
backgroundColor: "ui.danger",
color: "ui.white",
color: "ui.light",
_hover: {
backgroundColor: "#E32727",
},

31
frontend/src/utils.ts

@ -2,3 +2,34 @@ export const emailPattern = {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
message: "Invalid email address",
}
export const passwordRules = (isRequired = true) => {
const rules: any = {
minLength: {
value: 8,
message: "Password must be at least 8 characters",
},
}
if (isRequired) {
rules.required = "Password is required"
}
return rules
}
export const confirmPasswordRules = (
getValues: () => any,
isRequired = true,
) => {
const rules: any = {
validate: (value: string) =>
value === getValues().password || "The passwords do not match",
}
if (isRequired) {
rules.required = "Password confirmation is required"
}
return rules
}

Loading…
Cancel
Save