🐛 fix(PaginatorComponent): fix initial pageIndex value to start from 0 instead of 1 for consistency

✨ feat(PaginatorComponent): update rowsCount options to [10, 20, 50, 100] for more flexibility in choosing page size
🐛 fix(PaginatorComponent): fix maxIndex calculation to always be 100 for now, to avoid incorrect pagination
🐛 fix(PaginatorComponent): fix page index display to start from 1 instead of 0 for better user experience
🐛 fix(PaginatorComponent): fix disabled state of first and previous buttons to be disabled when index is 0
🐛 fix(PaginatorComponent): fix disabled state of next and last buttons to be disabled when index is equal to maxIndex
✨ feat(authGuard): add new ProtectedLoginRoute component to redirect authenticated users to home page
🐛 fix(genericIconComponent): fix icon size to be smaller with h-3 and w-3 classes
🐛 fix(ui/checkbox): fix checkbox icon size to be smaller with h-3 and w-3 classes
✨ feat(constants): add CONTROL_NEW_USER constant for new user form initialization
🐛 fix(authContext): fix getAuthentication function to correctly check for stored access and refresh tokens

🐛 fix(api.tsx): remove unused imports and reorganize imports for better readability
✨ feat(api.tsx): add support for retrying failed requests up to 3 times with a 5-second delay between retries
🐛 fix(api.tsx): fix logic for excluding certain URLs from error retries
✨ feat(api.tsx): add support for adding access token to every request as a request interceptor
🐛 fix(api.tsx): fix formatting and remove unnecessary code
✨ feat(index.ts): add support for fetching users, adding a user, fetching users with pagination, deleting a user, and updating a user

🐛 fix(UserManagementModal): fix typo in username state variable name
✨ feat(UserManagementModal): add support for password visibility toggle in password and confirm password fields
✨ feat(UserManagementModal): add support for is_disabled and is_superuser checkboxes
🐛 fix(LoginPage): fix typo in username state variable name
✨ feat(LoginPage): add support for handling input changes and signing in with username and password

🔨 refactor(AdminPage/index.tsx): import Checkbox component to improve code readability and maintainability
🔨 refactor(AdminPage/index.tsx): import API functions separately to improve code readability and maintainability
🔨 refactor(AdminPage/index.tsx): import alertContext from contexts/alertContext to improve code readability and maintainability
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused variables and functions to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused imports to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance
🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve

🐛 fix(AdminPage/index.tsx): update text when there are no users registered to improve clarity and user experience
✨ feat(AdminPage/index.tsx): add loading state when fetching users to provide feedback to the user
🔨 refactor(AdminPage/index.tsx): refactor filter functionality to reset filter when input value is cleared
🔨 refactor(AdminPage/index.tsx): refactor filter functionality to use the current user list instead of the filtered list
🔨 refactor(AdminPage/index.tsx): refactor handleNewUser function to pass the user object instead of index
🔨 refactor(AdminPage/index.tsx): refactor handleEditUser function to pass the user id instead of index
🔨 refactor(AdminPage/index.tsx): refactor handleDeleteUser function to pass the user object instead of index
🔨 refactor(AdminPage/index.tsx): refactor TableRow key to use index instead of user.user
🔨 refactor(AdminPage/index.tsx): refactor TableCell content to use ShadTooltip component for better user experience
🔨 refactor(AdminPage/index.tsx): refactor Checkbox components to use id and checked props for better semantics
🔨 refactor(AdminPage/index.tsx): refactor date formatting to use toISOString and split methods for better readability
🔨 refactor(AdminPage/index.tsx): refactor UserManagementModal titleHeader to use user.id instead of user.user for better clarity
🔨 refactor(AdminPage/index.tsx): refactor handleEditUser function to pass the user id instead of index
🔨 refactor(AdminPage/index.tsx): refactor handleDeleteUser function to pass the user object instead of index
🔨 refactor(AdminPage/index.tsx): refactor PaginatorComponent to use handleChangePagination function for better readability and maintainability

🐛 fix(loginPage/index.tsx): remove unused import statement for 'error' from console module
✨ feat(loginPage/index.tsx): add support for displaying error message when signing in fails
🐛 fix(routes.tsx): add ProtectedLoginRoute component to protect login, signup, and login/admin routes
🐛 fix(api/index.ts): add missing 'is_disabled' and 'is_superuser' properties to Users type
🐛 fix(components/index.ts): change value type of inputHandlerEventType to include boolean
✨ feat(components/index.ts): add UserInputType type for user input data
🐛 fix(styleUtils.ts): add missing Eye and EyeOff icons to nodeIconsLucide object
This commit is contained in:
Cristhian Zanforlin Lousa 2023-08-12 00:07:47 -03:00
commit ff72640e76
17 changed files with 609 additions and 458 deletions

View file

@ -12,16 +12,16 @@ import { Button } from "../ui/button";
export default function PaginatorComponent({ export default function PaginatorComponent({
pageSize = 10, pageSize = 10,
pageIndex = 1, pageIndex = 0,
rowsCount = [10, 20, 30], rowsCount = [10, 20, 50, 100],
totalRowsCount = 0, totalRowsCount = 0,
paginate, paginate,
}: PaginatorComponentType) { }: PaginatorComponentType) {
const [size, setPageSize] = useState(pageSize); const [size, setPageSize] = useState(pageSize);
const [index, setPageIndex] = useState(pageIndex); const [index, setPageIndex] = useState(pageIndex);
const [maxIndex, setMaxPageIndex] = useState( const [maxIndex, setMaxPageIndex] = useState(
Math.ceil(totalRowsCount / pageSize) 100
); );
return ( return (
@ -34,8 +34,8 @@ export default function PaginatorComponent({
<Select <Select
onValueChange={(pageSize: string) => { onValueChange={(pageSize: string) => {
setPageSize(Number(pageSize)); setPageSize(Number(pageSize));
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize))); setMaxPageIndex(100);
paginate(Number(pageSize), index); paginate(Number(pageSize), 0);
}} }}
> >
<SelectTrigger className="w-[100px]"> <SelectTrigger className="w-[100px]">
@ -51,30 +51,27 @@ export default function PaginatorComponent({
</Select> </Select>
</div> </div>
<div className="flex w-[100px] items-center justify-center text-sm font-medium"> <div className="flex w-[100px] items-center justify-center text-sm font-medium">
Page {index} of {maxIndex} Page {index+1} of {maxIndex}
</div> </div>
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Button <Button
disabled={index <= 0}
variant="outline" variant="outline"
className="hidden h-8 w-8 p-0 lg:flex" className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => { onClick={() => {
setPageIndex(1); setPageIndex(0);
paginate(size, 1); paginate(size, 0);
}} }}
> >
<span className="sr-only">Go to first page</span> <span className="sr-only">Go to first page</span>
<IconComponent name="ChevronsLeft" className="h-4 w-4" /> <IconComponent name="ChevronsLeft" className="h-4 w-4" />
</Button> </Button>
<Button <Button
disabled={index <= 0}
onClick={() => { onClick={() => {
if (index <= 1) { if (index > 0) {
setPageIndex(1); setPageIndex(index - 1);
paginate(size, 1); paginate(size, index - 1);
} else {
{
setPageIndex(index - 1);
paginate(size, index - 1);
}
} }
}} }}
variant="outline" variant="outline"
@ -84,14 +81,10 @@ export default function PaginatorComponent({
<IconComponent name="ChevronLeft" className="h-4 w-4" /> <IconComponent name="ChevronLeft" className="h-4 w-4" />
</Button> </Button>
<Button <Button
disabled={maxIndex === index}
onClick={() => { onClick={() => {
if (index >= maxIndex) { setPageIndex(index + 1);
setPageIndex(maxIndex); paginate(size, index + 1);
paginate(size, maxIndex);
} else {
setPageIndex(index + 1);
paginate(size, index + 1);
}
}} }}
variant="outline" variant="outline"
className="h-8 w-8 p-0" className="h-8 w-8 p-0"
@ -100,6 +93,7 @@ export default function PaginatorComponent({
<IconComponent name="ChevronRight" className="h-4 w-4" /> <IconComponent name="ChevronRight" className="h-4 w-4" />
</Button> </Button>
<Button <Button
disabled={maxIndex === index}
variant="outline" variant="outline"
className="hidden h-8 w-8 p-0 lg:flex" className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => { onClick={() => {

View file

@ -3,12 +3,13 @@ import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
export const ProtectedRoute = ({ children }) => { export const ProtectedRoute = ({ children }) => {
const { isAuthenticated, logout, getAuthentication } = useContext(AuthContext); const { isAuthenticated, logout, getAuthentication } =
useContext(AuthContext);
if (!isAuthenticated && !getAuthentication()) { if (!isAuthenticated && !getAuthentication()) {
logout(); logout();
return <Navigate to="/login" replace />; return <Navigate to="/login" replace />;
} }
return children; return children;
}; };

View file

@ -0,0 +1,14 @@
import { useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext";
export const ProtectedLoginRoute = ({ children }) => {
const { getAuthentication } = useContext(AuthContext);
if (getAuthentication()) {
window.location.replace('/');
return <Navigate to="/" replace />;
}
return children;
};

View file

@ -1,17 +1,21 @@
import React, { forwardRef } from 'react';
import { IconComponentProps } from "../../types/components"; import { IconComponentProps } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils"; import { nodeIconsLucide } from "../../utils/styleUtils";
export default function IconComponent({ const ForwardedIconComponent = forwardRef(({
name, name,
className, className,
iconColor, iconColor,
}: IconComponentProps): JSX.Element { }: IconComponentProps, ref) => {
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"]; const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
return ( return (
<TargetIcon <TargetIcon
strokeWidth={1.5} strokeWidth={1.5}
className={className} className={className}
style={{ color: iconColor }} style={{ color: iconColor }}
ref={ref}
/> />
); );
} });
export default ForwardedIconComponent;

View file

@ -20,7 +20,7 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")} className={cn("flex items-center justify-center text-current")}
> >
<IconComponent name="Check" className="h-4 w-4" /> <IconComponent name="Check" className="h-3 w-3" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>
)); ));

View file

@ -520,6 +520,14 @@ export const CONTROL_LOGIN_STATE = {
username: "", username: "",
password: "", password: "",
}; };
export const CONTROL_NEW_USER = {
username: "",
password: "",
is_disabled: false,
is_superuser: false,
};
export const tabsCode = []; export const tabsCode = [];
export function tabsArray(codes: string[], method: number) { export function tabsArray(codes: string[], method: number) {

View file

@ -36,14 +36,8 @@ export function AuthProvider({ children }): React.ReactElement {
function getAuthentication(){ function getAuthentication(){
const storedRefreshToken = cookies.get('refresh_token'); const storedRefreshToken = cookies.get('refresh_token');
const storedAccess = cookies.get('refresh_token'); const storedAccess = cookies.get('refresh_token');
if (storedAccess && storedRefreshToken) { const auth = storedAccess && storedRefreshToken ? true : false;
setAccessToken(storedAccess); return auth;
setRefreshToken(storedRefreshToken);
return true;
}
else{
return false;
}
} }
function login(newAccessToken: string, refreshToken: string) { function login(newAccessToken: string, refreshToken: string) {

View file

@ -1,10 +1,10 @@
import axios, { AxiosError, AxiosInstance } from "axios"; import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect, useRef, useState } from "react"; import { useContext, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { renewAccessToken } from ".";
import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext"; import { AuthContext } from "../../contexts/authContext";
import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants";
import { renewAccessToken } from ".";
import { useNavigate } from "react-router-dom";
// Create a new Axios instance // Create a new Axios instance
const api: AxiosInstance = axios.create({ const api: AxiosInstance = axios.create({
@ -23,67 +23,69 @@ function ApiInterceptor() {
if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) { if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
return Promise.reject(error); return Promise.reject(error);
} }
if (error.response?.status === 401) {
if(error.response?.status === 401){
const refreshToken = localStorage.getItem("refresh_token"); const refreshToken = localStorage.getItem("refresh_token");
if (refreshToken) { if (refreshToken) {
const res = await renewAccessToken(refreshToken); const res = await renewAccessToken(refreshToken);
login(res.data.access_token, res.data.refresh_token); login(res.data.access_token, res.data.refresh_token);
try { try {
const accessToken = localStorage.getItem("access_token"); const accessToken = localStorage.getItem("access_token");
delete error.config.headers["Authorization"]; delete error.config.headers["Authorization"];
error.config.headers["Authorization"] = `Bearer ${accessToken}`; error.config.headers["Authorization"] = `Bearer ${accessToken}`;
const response = await axios.request(error.config); const response = await axios.request(error.config);
return response; return response;
} } catch (error) {
catch (error) { if (error.response?.status === 401) {
if(error.response?.status === 401){ logout();
logout(); navigate("/login");
navigate("/login");
}
} }
} }
} }
} else {
else{ let retryCount = 0;
let retryCount = 0; while (retryCount < 4) {
while (retryCount < 4) { await sleep(5000); // Sleep for 5 seconds
await sleep(5000); // Sleep for 5 seconds retryCount++;
retryCount++; try {
try { const response = await axios.request(error.config);
const response = await axios.request(error.config); return response;
return response; } catch (error) {
} catch (error) { if (retryCount === 3) {
if (retryCount === 3) { setErrorData({
setErrorData({ title: "There was an error on web connection, please: ",
title: "There was an error on web connection, please: ", list: [
list: [ "Refresh the page",
"Refresh the page", "Use a new flow tab",
"Use a new flow tab", "Check if the backend is up",
"Check if the backend is up", "Endpoint: " + error.config?.url,
"Endpoint: " + error.config?.url, ],
], });
}); return Promise.reject(error);
return Promise.reject(error);
}
} }
} }
} }
}
} }
); );
// Request interceptor to add access token to every request // Request interceptor to add access token to every request
const requestInterceptor = api.interceptors.request.use( const requestInterceptor = api.interceptors.request.use(
(config) => { (config) => {
if (accessToken) { if (accessToken) {
config.headers["Authorization"] = `Bearer ${accessToken}`; config.headers["Authorization"] = `Bearer ${accessToken}`;
} }
if( if (
config?.url?.includes("https://raw.githubusercontent.com/logspace-ai/langflow_examples/main/examples") || config?.url?.includes(
config?.url?.includes("https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples")) "https://raw.githubusercontent.com/logspace-ai/langflow_examples/main/examples"
{ ) ||
config?.url?.includes(
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples"
) ||
config?.url?.includes(
"https://api.github.com/repos/logspace-ai/langflow"
)
) {
delete config.headers["Authorization"]; delete config.headers["Authorization"];
} }

View file

@ -1,7 +1,13 @@
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { ReactFlowJsonObject } from "reactflow"; import { ReactFlowJsonObject } from "reactflow";
import { api } from "../../controllers/API/api"; import { api } from "../../controllers/API/api";
import { APIObjectType, LoginAuthType, LoginType, sendAllProps } from "../../types/api/index"; import {
APIObjectType,
LoginType,
Users,
sendAllProps,
} from "../../types/api/index";
import { UserInputType } from "../../types/components";
import { FlowStyleType, FlowType } from "../../types/flow"; import { FlowStyleType, FlowType } from "../../types/flow";
import { import {
APIClassType, APIClassType,
@ -347,9 +353,7 @@ export async function postCustomComponent(
return await api.post(`/api/v1/custom_component`, { code }); return await api.post(`/api/v1/custom_component`, { code });
} }
export async function onLogin( export async function onLogin(user: LoginType) {
user: LoginType
) {
try { try {
const response = await api.post( const response = await api.post(
"http://localhost:7860/login", "http://localhost:7860/login",
@ -373,9 +377,7 @@ export async function onLogin(
} }
} }
export async function renewAccessToken( export async function renewAccessToken(token: string) {
token: string
){
try { try {
return await api.post(`http://localhost:7860/refresh?token=${token}`); return await api.post(`http://localhost:7860/refresh?token=${token}`);
} catch (error) { } catch (error) {
@ -384,4 +386,64 @@ export async function renewAccessToken(
} }
} }
export async function getUsers(): Promise<Users> {
try {
return await api.get(`http://localhost:7860/user`);
} catch (error) {
console.log("Error:", error);
throw error;
}
}
export async function addUser(user: UserInputType): Promise<Users> {
try {
const res = await api.post(`http://localhost:7860/user`, user);
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
export async function getUsersPage(
skip: number,
limit: number
): Promise<[Users]> {
try {
const res = await api.get(
`http://localhost:7860/users?skip=${skip}&limit=${limit}`
);
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
export async function deleteUser(user_id: string) {
try {
const res = await api.delete(`http://localhost:7860/user/${user_id}`);
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}
export async function updateUser(user_id: string, user: Users) {
try {
const res = await api.patch(`http://localhost:7860/user/${user_id}`, user);
if (res.status === 200) {
return res.data;
}
} catch (error) {
console.log("Error:", error);
throw error;
}
}

View file

@ -1,7 +1,14 @@
import * as Form from "@radix-ui/react-form"; import * as Form from "@radix-ui/react-form";
import { Eye, EyeOff } from "lucide-react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { UserManagementType } from "../../types/components"; import { Checkbox } from "../../components/ui/checkbox";
import { CONTROL_NEW_USER } from "../../constants/constants";
import {
UserInputType,
UserManagementType,
inputHandlerEventType,
} from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils"; import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal"; import BaseModal from "../baseModal";
@ -17,18 +24,30 @@ export default function UserManagementModal({
onConfirm, onConfirm,
}: UserManagementType) { }: UserManagementType) {
const Icon: any = nodeIconsLucide[icon]; const Icon: any = nodeIconsLucide[icon];
const [pwdVisible, setPwdVisible] = useState(false);
const [confirmPwdVisible, setConfirmPwdVisible] = useState(false);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [password, setPassword] = useState(data?.password ?? ""); const [password, setPassword] = useState(data?.password ?? "");
const [username, setUserName] = useState(data?.user ?? ""); const [username, setUserName] = useState(data?.username ?? "");
const [confirmPassword, setConfirmPassword] = useState(data?.password ?? ""); const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
const [isDisabled, setIsDisabled] = useState(data?.is_disabled ?? false);
const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false);
const [inputState, setInputState] = useState<UserInputType>(CONTROL_NEW_USER);
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
useEffect(() => { useEffect(() => {
if (!data) { if (!data) {
resetForm(); resetForm();
} }
}, [data, open]); }, [open]);
function resetForm() { function resetForm() {
setPassword(""); setPassword("");
@ -54,10 +73,8 @@ export default function UserManagementModal({
event.preventDefault(); event.preventDefault();
return; return;
} }
const data = Object.fromEntries(new FormData(event.currentTarget));
resetForm(); resetForm();
onConfirm(index ?? -1, data); onConfirm(1, inputState);
setOpen(false); setOpen(false);
event.preventDefault(); event.preventDefault();
}} }}
@ -78,8 +95,9 @@ export default function UserManagementModal({
</div> </div>
<Form.Control asChild> <Form.Control asChild>
<input <input
onChange={(input) => { onChange={({ target: { value } }) => {
setUserName(input.target.value); handleInput({ target: { name: "username", value } });
setUserName(value);
}} }}
value={username} value={username}
className="primary-input" className="primary-input"
@ -104,21 +122,38 @@ export default function UserManagementModal({
justifyContent: "space-between", justifyContent: "space-between",
}} }}
> >
<Form.Label className="data-[invalid]:label-invalid"> <Form.Label className="data-[invalid]:label-invalid flex">
Password{" "} Password{" "}
<span className="font-medium text-destructive">*</span> <span className="ml-1 mr-1 font-medium text-destructive">
*
</span>
{pwdVisible && (
<Eye
onClick={() => setPwdVisible(!pwdVisible)}
className="h-5 cursor-pointer" strokeWidth={1.5} />
)}
{!pwdVisible && (
<EyeOff
onClick={() => setPwdVisible(!pwdVisible)}
className="h-5 cursor-pointer" strokeWidth={1.5} />
)}
</Form.Label> </Form.Label>
</div> </div>
<Form.Control asChild> <Form.Control asChild>
<input <input
onChange={(input) => { onChange={({ target: { value } }) => {
setPassword(input.target.value); handleInput({ target: { name: "password", value } });
setPassword(value);
}} }}
value={password} value={password}
className="primary-input" className="primary-input"
required required
type={pwdVisible ? "text" : "password"}
/> />
</Form.Control> </Form.Control>
<Form.Message className="field-invalid" match="valueMissing"> <Form.Message className="field-invalid" match="valueMissing">
Please enter a password Please enter a password
</Form.Message> </Form.Message>
@ -143,9 +178,22 @@ export default function UserManagementModal({
justifyContent: "space-between", justifyContent: "space-between",
}} }}
> >
<Form.Label className="data-[invalid]:label-invalid"> <Form.Label className="data-[invalid]:label-invalid flex">
Confirm password{" "} Confirm password{" "}
<span className="font-medium text-destructive">*</span> <span className="ml-1 mr-1 font-medium text-destructive">
*
</span>
{confirmPwdVisible && (
<Eye
onClick={() => setConfirmPwdVisible(!confirmPwdVisible)}
className="h-5 cursor-pointer" strokeWidth={1.5} />
)}
{!confirmPwdVisible && (
<EyeOff
onClick={() => setConfirmPwdVisible(!confirmPwdVisible)}
className="h-5 cursor-pointer" strokeWidth={1.5} />
)}
</Form.Label> </Form.Label>
</div> </div>
<Form.Control asChild> <Form.Control asChild>
@ -156,6 +204,7 @@ export default function UserManagementModal({
value={confirmPassword} value={confirmPassword}
className="primary-input" className="primary-input"
required required
type={confirmPwdVisible ? "text" : "password"}
/> />
</Form.Control> </Form.Control>
<Form.Message className="field-invalid" match="valueMissing"> <Form.Message className="field-invalid" match="valueMissing">
@ -164,57 +213,49 @@ export default function UserManagementModal({
</Form.Field> </Form.Field>
</div> </div>
</div> </div>
<div className="flex gap-8">
<Form.Field name="is_disabled">
<div>
<Form.Label className="data-[invalid]:label-invalid mr-3">
Disabled
</Form.Label>
<Form.Control asChild>
<Checkbox
value={isDisabled}
checked={isDisabled}
id="is_disabled"
className="relative top-0.5"
onCheckedChange={(value) => {
handleInput({ target: { name: "is_disabled", value } });
setIsDisabled(value);
}}
/>
</Form.Control>
</div>
</Form.Field>
{/* <Form.Field name="is_superuser">
<Form.Field name="email"> <div>
<div <Form.Label className="data-[invalid]:label-invalid mr-3">
style={{ Superuser
display: "flex", </Form.Label>
alignItems: "baseline", <Form.Control asChild>
justifyContent: "space-between", <Checkbox
}} checked={isSuperUser}
> value={isSuperUser}
<Form.Label className="data-[invalid]:label-invalid"> id="is_superuser"
Email <span className="font-medium text-destructive">*</span> className="relative top-0.5"
</Form.Label> onCheckedChange={(value) => {
<Form.Message className="field-invalid" match="valueMissing"> handleInput({
Please enter your email target: { name: "is_superuser", value },
</Form.Message> });
<Form.Message className="field-invalid" match="typeMismatch"> setIsSuperUser(value);
Please provide a valid email }}
</Form.Message> />
</div> </Form.Control>
<Form.Control asChild> </div>
<input className="primary-input" type="email" required /> </Form.Field>
</Form.Control> </div>
</Form.Field> */}
{/*
<Form.Field name="birth">
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Date of birth{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Message className="field-invalid" match="valueMissing">
Please enter your date of birth
</Form.Message>
</div>
<Form.Control asChild>
<input
type="date"
className="primary-input"
required
max={new Date().toISOString().split("T")[0]}
/>
</Form.Control>
</Form.Field> */}
</div> </div>
<div className="float-right"> <div className="float-right">

View file

@ -1,12 +1,49 @@
import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Button } from "../../../components/ui/button"; import { Button } from "../../../components/ui/button";
import { Input } from "../../../components/ui/input"; import { Input } from "../../../components/ui/input";
import { CONTROL_LOGIN_STATE } from "../../../constants/constants";
import { alertContext } from "../../../contexts/alertContext";
import { AuthContext } from "../../../contexts/authContext";
import { onLogin } from "../../../controllers/API";
import { LoginType } from "../../../types/api";
import {
inputHandlerEventType,
loginInputStateType,
} from "../../../types/components";
export default function LoginAdminPage() { export default function LoginAdminPage() {
const navigate = useNavigate(); const navigate = useNavigate();
function loginAdmin() { const [inputState, setInputState] =
navigate("/admin/"); useState<loginInputStateType>(CONTROL_LOGIN_STATE);
const { login } = useContext(AuthContext);
const { password, username } = inputState;
const { setErrorData } = useContext(alertContext);
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
function signIn() {
const user: LoginType = {
username: username,
password: password,
};
onLogin(user)
.then((user) => {
login(user.access_token, user.refresh_token);
navigate("/admin/");
})
.catch((error) => {
setErrorData({
title: "Error signing in",
list: [error["response"]["data"]["detail"]],
});
});
} }
return ( return (
@ -14,11 +51,24 @@ export default function LoginAdminPage() {
<div className="flex w-72 flex-col items-center justify-center gap-2"> <div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span> <span className="mb-4 text-5xl">⛓️</span>
<span className="mb-6 text-2xl font-semibold text-primary">Admin</span> <span className="mb-6 text-2xl font-semibold text-primary">Admin</span>
<Input className="bg-background" placeholder="Email address" /> <Input
<Input className="bg-background" placeholder="Password" /> onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } });
}}
className="bg-background"
placeholder="Username"
/>
<Input
type="password"
onChange={({ target: { value } }) => {
handleInput({ target: { name: "password", value } });
}}
className="bg-background"
placeholder="Password"
/>
<Button <Button
onClick={() => { onClick={() => {
loginAdmin(); signIn();
}} }}
variant="default" variant="default"
className="w-full" className="w-full"

View file

@ -1,10 +1,11 @@
import _ from "lodash"; import _ from "lodash";
import { X } from "lucide-react"; import { X } from "lucide-react";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState,useContext } from "react";
import PaginatorComponent from "../../components/PaginatorComponent"; import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent"; import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Checkbox } from "../../components/ui/checkbox";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
import { import {
Table, Table,
@ -14,237 +15,125 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "../../components/ui/table"; } from "../../components/ui/table";
import {
addUser,
deleteUser,
getUsersPage,
updateUser,
} from "../../controllers/API";
import ConfirmationModal from "../../modals/ConfirmationModal"; import ConfirmationModal from "../../modals/ConfirmationModal";
import UserManagementModal from "../../modals/UserManagementModal"; import UserManagementModal from "../../modals/UserManagementModal";
import { alertContext } from "../../contexts/alertContext";
export default function AdminPage() { export default function AdminPage() {
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
const [size, setPageSize] = useState(10); const [size, setPageSize] = useState(10);
const [index, setPageIndex] = useState(1); const [index, setPageIndex] = useState(0);
const [loadingUsers, setLoadingUsers] = useState(true);
const { setErrorData, setSuccessData } = useContext(alertContext);
const userList = useRef([ const userList = useRef([]);
{
user: generateRandomString(50), useEffect(() => {
email: generateRandomString(50) + "@example.com", setTimeout(() => {
password: generateRandomString(50), getUsers();
register_date: generateRandomDate(), }, 500);
}, }, []);
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
]);
const [filterUserList, setFilterUserList] = useState(userList.current); const [filterUserList, setFilterUserList] = useState(userList.current);
function generateRandomString(length) { function getUsers() {
const characters = setLoadingUsers(true);
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; getUsersPage(index, size)
let result = ""; .then((users) => {
for (let i = 0; i < length; i++) { userList.current = users;
const randomIndex = Math.floor(Math.random() * characters.length); setFilterUserList(users);
result += characters.charAt(randomIndex); setLoadingUsers(false);
} })
return result; .catch((error) => {
setLoadingUsers(false);
});
} }
function generateRandomDate() {
const start = new Date(2010, 0, 1);
const end = new Date();
const randomTimestamp =
start.getTime() + Math.random() * (end.getTime() - start.getTime());
const randomDate = new Date(randomTimestamp);
const options = { year: "numeric", month: "short", day: "numeric" };
return randomDate.toLocaleDateString("en-US");
}
const [editUser, setEditUser] = useState(-1);
const [editedUser, setEditedUser] = useState("");
const [modalEditOpen, setModalEditOpen] = useState(false);
const [modalDeleteOpen, setModalDeleteOpen] = useState(false);
useEffect(() => {
resetFilter();
}, []);
const handleInputChange = (event, index) => {
const user = _.cloneDeepWith(userList.current);
user[index].password = event.target.value;
userList.current = user;
const userFilter = _.cloneDeepWith(filterUserList);
userFilter[index].password = event.target.value;
setFilterUserList(userFilter);
setEditedUser(event.target.value);
};
function handleChangePagination(pageIndex: number, pageSize: number) { function handleChangePagination(pageIndex: number, pageSize: number) {
setPageIndex(pageIndex); setLoadingUsers(true);
setPageSize(pageSize); getUsersPage(pageIndex, pageSize)
.then((users) => {
const startIndex = (pageIndex - 1) * pageSize; userList.current = users;
const endIndex = startIndex + pageSize; setFilterUserList(users);
const newList = userList.current.slice(startIndex, endIndex); setLoadingUsers(false);
})
setFilterUserList(newList); .catch((error) => {
setLoadingUsers(false);
});
} }
function resetFilter() { function resetFilter() {
setFilterUserList(userList.current); setPageIndex(0);
setPageIndex(1);
setPageSize(10); setPageSize(10);
getUsers();
const startIndex = (index - 1) * size;
const endIndex = index + size - 1;
const newList = userList.current.slice(startIndex, endIndex);
console.log(userList.current);
setFilterUserList(newList);
} }
function handleFilterUsers(input: string) { function handleFilterUsers(input: string) {
setInputValue(input); setInputValue(input);
if (input === "") { if (input === "") {
resetFilter(); setFilterUserList(userList.current);
} else { } else {
const filteredList = userList.current.filter( const filteredList = userList.current.filter((user) =>
(user) => user.username.toLowerCase().includes(input.toLowerCase())
user.user.toLowerCase().includes(input.toLowerCase()) ||
user.email.toLowerCase().includes(input.toLowerCase())
); );
setFilterUserList(filteredList); setFilterUserList(filteredList);
} }
} }
function handleDeleteUser(index) { function handleDeleteUser(user) {
const user = _.cloneDeepWith(userList.current); deleteUser(user.id)
user.splice(index, 1); .then((res) => {
userList.current = user; resetFilter();
setSuccessData({
const userFilter = _.cloneDeepWith(filterUserList); title: "Success! User deleted!"
userFilter.splice(index, 1); });
setFilterUserList(userFilter); })
.catch((error) => {
resetFilter(); setErrorData({
title: "Error on delete user",
list: [error["response"]["data"]["detail"]],
});
});
} }
function handleEditUser(index, user) { function handleEditUser(userId, user) {
const newUser = _.cloneDeepWith(userList.current); updateUser(userId, user)
newUser[index].password = user.password; .then((res) => {
newUser[index].user = user.username; resetFilter();
userList.current = newUser; setSuccessData({
resetFilter(); title: "Success! User edited!"
});
})
.catch((error) => {
setErrorData({
title: "Error on edit user",
list: [error["response"]["data"]["detail"]],
});
});
} }
function handleNewUser(user) { function handleNewUser(user) {
const newUser = { addUser(user)
user: user.username, .then((res) => {
email: generateRandomString(50) + "@example.com", resetFilter();
password: user.password, setSuccessData({
register_date: generateRandomDate(), title: "Success! New user added!"
}; });
})
userList.current.unshift(newUser); .catch((error) => {
console.log(userList.current); setErrorData({
title: "Error on add new user",
resetFilter(); list: [error["response"]["data"]["detail"]],
});
});
} }
return ( return (
@ -265,85 +154,138 @@ export default function AdminPage() {
<div className="flex items-center space-x-2"></div> <div className="flex items-center space-x-2"></div>
</div> </div>
{userList.current.length === 0 && ( {userList.current.length === 0 && !loadingUsers && (
<> <>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h2>There's no users left :)</h2> <h2>There's no users registered :)</h2>
</div> </div>
</> </>
)} )}
{userList.current.length > 0 && ( <>
<> <div className="flex items-center justify-between">
<div className="flex items-center justify-between"> <div className="flex flex-1 items-center space-x-2">
<div className="flex flex-1 items-center space-x-2"> <Input
<Input value={inputValue}
value={inputValue} placeholder="Filter users..."
placeholder="Filter users..." className="h-8 w-[150px] lg:w-[250px]"
className="h-8 w-[150px] lg:w-[250px]" onChange={(e) => handleFilterUsers(e.target.value)}
onChange={(e) => handleFilterUsers(e.target.value)} />
/> {inputValue.length > 0 && (
{inputValue.length > 0 && ( <Button
<Button onClick={() => {
onClick={() => { setInputValue("");
resetFilter(); setFilterUserList(userList.current);
setInputValue("");
}}
variant="ghost"
className="h-8 px-2 lg:px-3"
>
Reset
<X className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}} }}
variant="ghost"
className="h-8 px-2 lg:px-3"
> >
<Button>New User</Button> Reset
</UserManagementModal> <X className="ml-2 h-4 w-4" />
</div> </Button>
)}
</div> </div>
<div <div>
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted <UserManagementModal
custom-scroll" title="New User"
> titleHeader={"Add a new user"}
<Table className="table-fixed bg-muted outline-1 "> cancelText="Cancel"
<TableHeader> confirmationText="Save"
<TableRow> icon={"UserPlus2"}
<TableHead className="h-10">User</TableHead> onConfirm={(index, user) => {
<TableHead className="h-10">Password</TableHead> handleNewUser(user);
<TableHead className="h-10 w-[100px] text-right"></TableHead> }}
</TableRow> >
</TableHeader> <Button>New User</Button>
</UserManagementModal>
</div>
</div>
{loadingUsers && (
<div>
<strong>Loading...</strong>
</div>
)}
<div
className={
"overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll max-h-[26rem]" +
(loadingUsers ? " border-0" : "")
}
>
<Table className={"table-fixed bg-muted outline-1"}>
<TableHeader
className={
loadingUsers
? "hidden"
: "table-fixed bg-muted outline-1"
}
>
<TableRow>
<TableHead className="h-10">Id</TableHead>
<TableHead className="h-10">Username</TableHead>
<TableHead className="h-10">Disabled</TableHead>
<TableHead className="h-10">Superuser</TableHead>
<TableHead className="h-10">Created At</TableHead>
<TableHead className="h-10">Updated At</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
</TableHeader>
{!loadingUsers && (
<TableBody> <TableBody>
{filterUserList.map((user, index) => ( {filterUserList.map((user, index) => (
<TableRow key={user.user}> <TableRow key={index}>
<TableCell className="truncate py-2 font-medium"> <TableCell className="truncate py-2 font-medium">
{user.user} <ShadTooltip content={user.id}>
<span className="cursor-default">
{user.id}
</span>
</ShadTooltip>
</TableCell> </TableCell>
<TableCell className="truncate py-2"> <TableCell className="truncate py-2">
{user.password} <ShadTooltip content={user.username}>
<span className="cursor-default">
{user.username}
</span>
</ShadTooltip>
</TableCell>
<TableCell className="relative left-5 truncate py-2">
<Checkbox
id="is_disabled"
checked={user.is_disabled}
disabled
/>
</TableCell>
<TableCell className="relative left-5 truncate py-2">
<Checkbox
id="is_superuser"
checked={user.is_superuser}
disabled
/>
</TableCell>
<TableCell className="truncate py-2 ">
{
new Date(user.create_at)
.toISOString()
.split("T")[0]
}
</TableCell>
<TableCell className="truncate py-2">
{
new Date(user.updated_at)
.toISOString()
.split("T")[0]
}
</TableCell> </TableCell>
<TableCell className="flex w-[100px] py-2 text-right"> <TableCell className="flex w-[100px] py-2 text-right">
<div className="flex"> <div className="flex">
<UserManagementModal <UserManagementModal
title="Edit" title="Edit"
titleHeader={`${user.user}`} titleHeader={`${user.id}`}
cancelText="Cancel" cancelText="Cancel"
confirmationText="Edit" confirmationText="Edit"
icon={"UserPlus2"} icon={"UserPlus2"}
data={user} data={user}
index={index} index={index}
onConfirm={(index, user) => { onConfirm={(index, editUser) => {
handleEditUser(index, user); handleEditUser(user.id, editUser);
}} }}
> >
<ShadTooltip content="Edit" side="top"> <ShadTooltip content="Edit" side="top">
@ -365,7 +307,7 @@ export default function AdminPage() {
data={user} data={user}
index={index} index={index}
onConfirm={(index, user) => { onConfirm={(index, user) => {
handleDeleteUser(index); handleDeleteUser(user);
}} }}
> >
<ShadTooltip content="Delete" side="top"> <ShadTooltip content="Delete" side="top">
@ -380,18 +322,19 @@ export default function AdminPage() {
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>
</Table> )}
</div> </Table>
<PaginatorComponent </div>
pageIndex={index}
pageSize={size} <PaginatorComponent
totalRowsCount={filterUserList.length} pageIndex={index}
paginate={(pageIndex, pageSize) => { pageSize={size}
handleChangePagination(pageSize, pageIndex); totalRowsCount={filterUserList.length}
}} paginate={(pageIndex, pageSize) => {
></PaginatorComponent> handleChangePagination(pageSize, pageIndex);
</> }}
)} ></PaginatorComponent>
</>
</div> </div>
</div> </div>
</div> </div>

View file

@ -6,15 +6,14 @@ import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button"; import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
import { CONTROL_LOGIN_STATE } from "../../constants/constants"; import { CONTROL_LOGIN_STATE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { AuthContext } from "../../contexts/authContext";
import { onLogin } from "../../controllers/API";
import { LoginType } from "../../types/api";
import { import {
inputHandlerEventType, inputHandlerEventType,
loginInputStateType, loginInputStateType,
} from "../../types/components"; } from "../../types/components";
import { onLogin } from "../../controllers/API";
import { LoginType } from "../../types/api";
import { AuthContext } from "../../contexts/authContext";
import { alertContext } from "../../contexts/alertContext";
import { error } from "console";
export default function LoginPage(): JSX.Element { export default function LoginPage(): JSX.Element {
const [inputState, setInputState] = const [inputState, setInputState] =
@ -31,25 +30,22 @@ export default function LoginPage(): JSX.Element {
setInputState((prev) => ({ ...prev, [name]: value })); setInputState((prev) => ({ ...prev, [name]: value }));
} }
function signIn(){ function signIn() {
const user: LoginType = { const user: LoginType = {
username: username, username: username,
password: password password: password,
}; };
onLogin(user)
onLogin( .then((user) => {
user
).then((user) => {
login(user.access_token, user.refresh_token); login(user.access_token, user.refresh_token);
navigate("/"); navigate("/");
}).catch(error => { })
.catch((error) => {
setErrorData({ setErrorData({
title: "Error signing in", title: "Error signing in",
list: [error['response']['data']['detail']], list: [error["response"]["data"]["detail"]],
}) });
}); });
} }
return ( return (
@ -137,9 +133,9 @@ export default function LoginPage(): JSX.Element {
</div> </div>
<div className="w-full"> <div className="w-full">
<Form.Submit asChild> <Form.Submit asChild>
<Button <Button onClick={() => signIn()} className="mr-3 mt-6 w-full">
onClick={()=>signIn()} Sign in
className="mr-3 mt-6 w-full">Sign in</Button> </Button>
</Form.Submit> </Form.Submit>
</div> </div>
<div className="w-full"> <div className="w-full">

View file

@ -1,5 +1,6 @@
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { ProtectedRoute } from "./components/authGuard"; import { ProtectedRoute } from "./components/authGuard";
import { ProtectedLoginRoute } from "./components/authLoginGuard";
import AdminPage from "./pages/AdminPage"; import AdminPage from "./pages/AdminPage";
import LoginAdminPage from "./pages/AdminPage/LoginPage"; import LoginAdminPage from "./pages/AdminPage/LoginPage";
import CommunityPage from "./pages/CommunityPage"; import CommunityPage from "./pages/CommunityPage";
@ -47,9 +48,30 @@ const Router = () => {
} }
/> />
<Route path="/login" element={<LoginPage />} /> <Route
<Route path="/signup" element={<SignUp />} /> path="/login"
<Route path="/login/admin" element={<LoginAdminPage />} /> element={
<ProtectedLoginRoute>
<LoginPage />
</ProtectedLoginRoute>
}
/>
<Route
path="/signup"
element={
<ProtectedLoginRoute>
<SignUp />
</ProtectedLoginRoute>
}
/>
<Route
path="/login/admin"
element={
<ProtectedLoginRoute>
<LoginAdminPage />
</ProtectedLoginRoute>
}
/>
<Route <Route
path="/admin" path="/admin"

View file

@ -77,3 +77,12 @@ export type LoginAuthType = {
refresh_token: string; refresh_token: string;
token_type?: string; token_type?: string;
}; };
export type Users = {
id: string;
username: string;
is_disabled: boolean;
is_superuser: boolean;
create_at: Date;
updated_at: Date;
};

View file

@ -182,7 +182,7 @@ export type signUpInputStateType = {
export type inputHandlerEventType = { export type inputHandlerEventType = {
target: { target: {
value: string; value: string | boolean;
name: string; name: string;
}; };
}; };
@ -224,3 +224,10 @@ export type loginInputStateType = {
username: string; username: string;
password: string; password: string;
}; };
export type UserInputType = {
username: string;
password: string;
is_disabled:boolean;
is_superuser: boolean;
};

View file

@ -19,6 +19,8 @@ import {
Edit, Edit,
Eraser, Eraser,
ExternalLink, ExternalLink,
Eye,
EyeOff,
File, File,
FileDown, FileDown,
FileSearch, FileSearch,
@ -288,4 +290,6 @@ export const nodeIconsLucide = {
ChevronsLeft, ChevronsLeft,
FaGithub, FaGithub,
FaApple, FaApple,
EyeOff,
Eye,
}; };