diff --git a/package-lock.json b/package-lock.json
index 571f529..38bd184 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2176,6 +2176,7 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
+ "extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {
diff --git a/src/App.tsx b/src/App.tsx
index 45a3ec7..0314c5b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,6 +5,12 @@ import NotFound from "./pages/NotFound"
import { AuthProvider } from "./hooks/useAuth"
import { ChakraProvider } from "@chakra-ui/react"
import { system } from "./theme"
+import Profile from "./pages/Profile"
+import ProfileEdit from "./pages/ProfileEdit"
+import Warnings from "./pages/Warnings"
+import Loans from "./pages/Loans"
+import { Toaster } from "./components/ui/toaster"
+import PrivateRoute from './PrivateRoute';
function App() {
return (
@@ -13,11 +19,16 @@ function App() {
} />
- } />
} />
+ } />
+ } />
+ } />
+ } />
+ } />
+
)
}
diff --git a/src/PrivateRoute.tsx b/src/PrivateRoute.tsx
new file mode 100644
index 0000000..02c55af
--- /dev/null
+++ b/src/PrivateRoute.tsx
@@ -0,0 +1,17 @@
+import { useNavigate } from "react-router";
+import { useAuth } from "./hooks/useAuth"
+import { useEffect } from "react";
+
+const PrivateRoute = ({ children }: { children: any }) => {
+ const navigate = useNavigate();
+ const { token } = useAuth();
+
+ useEffect(() => {
+ if (token) return;
+ navigate('/cadastro');
+ })
+
+ return children;
+}
+
+export default PrivateRoute
\ No newline at end of file
diff --git a/src/components/NavBar/index.tsx b/src/components/NavBar/index.tsx
new file mode 100644
index 0000000..694c1e1
--- /dev/null
+++ b/src/components/NavBar/index.tsx
@@ -0,0 +1,39 @@
+import { Box, Center, Flex, Stack, Text } from "@chakra-ui/react"
+import { Button } from "../ui/button"
+import { BsHouse, BsBell, BsBook, BsPerson } from 'react-icons/bs';
+import { useLocation, useNavigate } from "react-router";
+
+export const NavBar = () => {
+ const navigate = useNavigate()
+ const location = useLocation()
+
+ const selectedTab = location.pathname.replace('/', '');
+ const tabs = [
+ { label: 'Início', value: 'inicio', icon: },
+ { label: 'Empréstimos', value: 'emprestimos', icon: },
+ { label: 'Avisos', value: 'avisos', icon: },
+ { label: 'Perfil', value: 'perfil', icon: },
+ ]
+
+ return (
+
+
+
+ {tabs.map(tab => (
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx
new file mode 100644
index 0000000..df6c2c3
--- /dev/null
+++ b/src/components/ui/toaster.tsx
@@ -0,0 +1,43 @@
+"use client"
+
+import {
+ Toaster as ChakraToaster,
+ Portal,
+ Spinner,
+ Stack,
+ Toast,
+ createToaster,
+} from "@chakra-ui/react"
+
+export const toaster = createToaster({
+ placement: "bottom-end",
+ pauseOnPageIdle: true,
+})
+
+export const Toaster = () => {
+ return (
+
+
+ {(toast) => (
+
+ {toast.type === "loading" ? (
+
+ ) : (
+
+ )}
+
+ {toast.title && {toast.title}}
+ {toast.description && (
+ {toast.description}
+ )}
+
+ {toast.action && (
+ {toast.action.label}
+ )}
+ {toast.meta?.closable && }
+
+ )}
+
+
+ )
+}
diff --git a/src/hooks/useApi/index.tsx b/src/hooks/useApi/index.tsx
index 02c2ad0..16e343f 100644
--- a/src/hooks/useApi/index.tsx
+++ b/src/hooks/useApi/index.tsx
@@ -15,10 +15,8 @@ const createApiInstance = (url: string) => {
const getDefaultErrorUseAPIMessage = (err: any) => {
return {
error: true,
- ...err?.toJSON?.call(),
...err?.response,
...err?.response?.data,
- ...err?.data,
};
};
@@ -30,6 +28,18 @@ const useApi = () => {
);
return {
+ getProfile: (token: string): Promise<{ data: User }> => {
+ return new Promise((resolve) => {
+ api
+ .get('/auth/profile', {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ }
+ })
+ .then((res) => resolve(res))
+ .catch((err) => resolve(getDefaultErrorUseAPIMessage(err)));
+ });
+ },
signUp: (data: {
firstName: string;
lastName: string;
@@ -46,6 +56,33 @@ const useApi = () => {
.then((res) => resolve(res))
.catch((err) => resolve(getDefaultErrorUseAPIMessage(err)));
});
+ },
+ editProfile: async (id: string, data: {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phone: string;
+ oldPassword?: string
+ newPassword?: string
+ }): Promise<{ data: {
+ id: string;
+ } }> => {
+ return new Promise((resolve) => {
+ api
+ .put(`/users/${id}`, data)
+ .then((res) => resolve(res))
+ .catch((err) => resolve(getDefaultErrorUseAPIMessage(err)));
+ });
+ },
+ deleteProfile: async (id: string): Promise<{ data: {
+ id: string;
+ } }> => {
+ return new Promise((resolve) => {
+ api
+ .delete(`/users/${id}`)
+ .then((res) => resolve(res))
+ .catch((err) => resolve(getDefaultErrorUseAPIMessage(err)));
+ });
}
}
}
diff --git a/src/hooks/useAuth.tsx b/src/hooks/useAuth.tsx
index 320e7a8..afec0e3 100644
--- a/src/hooks/useAuth.tsx
+++ b/src/hooks/useAuth.tsx
@@ -3,6 +3,7 @@
import { createContext, useState, useContext, ReactNode } from 'react';
import useApi from './useApi';
+import { toaster } from '../components/ui/toaster';
interface SignUpParams {
firstName: string;
@@ -12,17 +13,27 @@ interface SignUpParams {
password: string;
}
+interface EditProfileParams {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phone: string;
+ oldPassword?: string;
+ newPassword?: string;
+}
+
type AuthContextType = {
isAuthenticated: boolean;
token: string | null;
signOut: () => void;
- signUp: (userToSignUp: SignUpParams) => Promise;
+ signUp: (userToSignUp: SignUpParams) => Promise;
+ editProfile: (id: string, profileToEdit: EditProfileParams) => Promise;
};
const AuthContext = createContext({} as AuthContextType);
export const AuthProvider = ({ children }: { children: ReactNode }) => {
- const { signUp: authSignUp } = useApi();
+ const { signUp: authSignUp, editProfile: authEditProfile } = useApi();
const localToken =
typeof window !== 'undefined'
@@ -32,13 +43,38 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
localToken ? localToken : null,
);
- async function signUp(userToSignUp: SignUpParams) {
+ async function signUp(userToSignUp: SignUpParams): Promise {
const { data } = await authSignUp(userToSignUp);
- if (!data.accessToken) return;
+ if (!data.accessToken) {
+ toaster.create({
+ title: 'Erro ao criar conta',
+ description: 'Verifique os campos e tente novamente.',
+ type: 'error',
+ })
+ return false;
+ };
setToken(data.accessToken);
if (typeof window !== 'undefined') {
localStorage.setItem('@livrolivre:token', data.accessToken);
}
+ return true;
+ }
+
+ async function editProfile(id: string, profileToEdit: EditProfileParams): Promise {
+ const { data } = await authEditProfile(id, profileToEdit);
+ if (data.id) {
+ toaster.create({
+ title: 'Perfil editado com sucesso!',
+ type: 'success',
+ })
+ return true;
+ };
+ toaster.create({
+ title: 'Erro ao editar perfil',
+ description: 'Verifique os campos e tente novamente.',
+ type: 'error',
+ })
+ return false;
}
function signOut(): void {
@@ -55,6 +91,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
token,
signOut,
signUp,
+ editProfile,
}}
>
{children}
diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts
index 4e95785..5494ac9 100644
--- a/src/interfaces/user.ts
+++ b/src/interfaces/user.ts
@@ -2,6 +2,7 @@ export interface User {
id: string;
firstName: string;
lastName: string;
+ phone: string;
email: string;
createdAt: string;
updatedAt: string;
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
index e56e6c1..df41cf7 100644
--- a/src/pages/Home/index.tsx
+++ b/src/pages/Home/index.tsx
@@ -1,10 +1,11 @@
-import styles from './styles';
+import { Box } from '@chakra-ui/react';
+import { NavBar } from '../../components/NavBar';
function Home() {
return (
-
- Home
-
+
+
+
);
}
diff --git a/src/pages/Home/styles.ts b/src/pages/Home/styles.ts
deleted file mode 100644
index 3667ffd..0000000
--- a/src/pages/Home/styles.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import styled from 'styled-components';
-
-const Container = styled.div``;
-
-export default { Container };
diff --git a/src/pages/Loans/index.tsx b/src/pages/Loans/index.tsx
new file mode 100644
index 0000000..4adb083
--- /dev/null
+++ b/src/pages/Loans/index.tsx
@@ -0,0 +1,12 @@
+import { Box } from '@chakra-ui/react';
+import { NavBar } from '../../components/NavBar';
+
+function Loans() {
+ return (
+
+
+
+ );
+}
+
+export default Loans
diff --git a/src/pages/Profile/DeleteProfileDialog/index.tsx b/src/pages/Profile/DeleteProfileDialog/index.tsx
new file mode 100644
index 0000000..7011fc9
--- /dev/null
+++ b/src/pages/Profile/DeleteProfileDialog/index.tsx
@@ -0,0 +1,68 @@
+import { HStack, Text } from "@chakra-ui/react"
+import {
+ DialogContent,
+ DialogRoot,
+ DialogTitle,
+ DialogTrigger,
+ DialogHeader,
+ DialogFooter,
+ DialogBody,
+ DialogActionTrigger,
+ DialogCloseTrigger,
+} from "../../../components/ui/dialog"
+import { Button } from "../../../components/ui/button"
+import useApi from "../../../hooks/useApi"
+import { useAuth } from "../../../hooks/useAuth"
+import { useNavigate } from "react-router"
+
+const DeleteProfileDialog = () => {
+ const navigate = useNavigate();
+ const { deleteProfile, getProfile } = useApi();
+ const { token, signOut } = useAuth();
+
+ const handleDelete = async () => {
+ console.log('maia', 123)
+ if (!token) return;
+ const { data } = await getProfile(token);
+ await deleteProfile(data.id);
+ signOut();
+ navigate('/login');
+ }
+
+ return (
+
+
+
+
+
+
+
+ Excluir conta
+
+
+
+ Tem certeza que deseja excluir sua conta? Essa ação é irreversível.
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default DeleteProfileDialog
\ No newline at end of file
diff --git a/src/pages/Profile/index.tsx b/src/pages/Profile/index.tsx
new file mode 100644
index 0000000..e09ef94
--- /dev/null
+++ b/src/pages/Profile/index.tsx
@@ -0,0 +1,54 @@
+import { Box, Center, Text, Stack } from '@chakra-ui/react';
+import { NavBar } from '../../components/NavBar';
+import { Button } from '../../components/ui/button';
+import { useNavigate } from 'react-router';
+import DeleteProfileDialog from './DeleteProfileDialog';
+import { useAuth } from '../../hooks/useAuth';
+
+function Profile() {
+
+ const navigate = useNavigate();
+ const { signOut } = useAuth();
+
+ return (
+
+
+
+
+ Perfil
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Profile
diff --git a/src/pages/ProfileEdit/ProfileEditForm/index.tsx b/src/pages/ProfileEdit/ProfileEditForm/index.tsx
new file mode 100644
index 0000000..4109dd9
--- /dev/null
+++ b/src/pages/ProfileEdit/ProfileEditForm/index.tsx
@@ -0,0 +1,143 @@
+import { useEffect, useState } from 'react';
+import { useAuth } from '../../../hooks/useAuth';
+import { Input, Stack } from '@chakra-ui/react';
+import { useForm } from 'react-hook-form';
+import { PasswordInput } from '../../../components/ui/password-input';
+import { Button } from '../../../components/ui/button';
+import useApi from '../../../hooks/useApi';
+import { Field } from '../../../components/ui/field';
+
+interface FormValues {
+ firstName: string;
+ lastName: string;
+ email: string;
+ phone: string;
+ oldPassword?: string;
+ newPassword?: string;
+ newPasswordConfirmation?: string;
+}
+
+function SignUpForm() {
+ const [userId, setUserId] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ watch,
+ formState: { errors, isValid },
+ } = useForm();
+
+ const { editProfile, token } = useAuth();
+ const { getProfile } = useApi();
+
+ const getUserData = async () => {
+ if (!token) return;
+ const { data } = await getProfile(token);
+ setValue('firstName', data.firstName);
+ setValue('lastName', data.lastName);
+ setValue('email', data.email);
+ setValue('phone', data.phone);
+ setUserId(data.id)
+ }
+
+ useEffect(() => {
+ getUserData();
+ }, [])
+
+ const onSubmit = handleSubmit(async (data: FormValues) => {
+ setLoading(true);
+ await editProfile(userId, {
+ firstName: data.firstName,
+ lastName: data.lastName,
+ email: data.email,
+ phone: data.phone,
+ newPassword: data.newPassword,
+ oldPassword: data.oldPassword,
+ });
+ setLoading(false);
+ })
+
+ return (
+
+ );
+}
+
+export default SignUpForm
diff --git a/src/pages/ProfileEdit/ProfileEditHeader/index.tsx b/src/pages/ProfileEdit/ProfileEditHeader/index.tsx
new file mode 100644
index 0000000..490d3e4
--- /dev/null
+++ b/src/pages/ProfileEdit/ProfileEditHeader/index.tsx
@@ -0,0 +1,18 @@
+import { Flex, Stack, Text } from '@chakra-ui/react';
+import { BsArrowLeftShort } from 'react-icons/bs';
+import { useNavigate } from 'react-router';
+
+function SignUpHeader() {
+ const navigate = useNavigate();
+ return (
+
+ navigate('/perfil')}>
+
+ Voltar
+
+ Editar perfil
+
+ );
+}
+
+export default SignUpHeader
diff --git a/src/pages/ProfileEdit/index.tsx b/src/pages/ProfileEdit/index.tsx
new file mode 100644
index 0000000..4bbb2ca
--- /dev/null
+++ b/src/pages/ProfileEdit/index.tsx
@@ -0,0 +1,18 @@
+import { Box, Center, Stack } from '@chakra-ui/react';
+import ProfileEditForm from './ProfileEditForm';
+import ProfileEditHeader from './ProfileEditHeader';
+
+function ProfileEdit() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ProfileEdit
diff --git a/src/pages/SignUp/SignUpForm/index.tsx b/src/pages/SignUp/SignUpForm/index.tsx
index 4b3fab8..08805ad 100644
--- a/src/pages/SignUp/SignUpForm/index.tsx
+++ b/src/pages/SignUp/SignUpForm/index.tsx
@@ -5,6 +5,7 @@ import { Input, Stack } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { PasswordInput } from '../../../components/ui/password-input';
import { Button } from '../../../components/ui/button';
+import { Field } from '../../../components/ui/field';
interface FormValues {
firstName: string;
@@ -22,6 +23,7 @@ function SignUpForm() {
const {
register,
handleSubmit,
+ formState: { errors, isValid },
} = useForm();
const { signUp, token } = useAuth();
@@ -40,43 +42,67 @@ function SignUpForm() {
useEffect(() => {
if (!token) return;
- navigate('/');
+ navigate('/inicio');
}, [token])
return (