From f695619f6bf1899bc5ef7d90ff04d31e45e23ab4 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 10:46:17 -0400 Subject: [PATCH 01/23] Se hace uso del estado global Auth para realizar el login, se hable un handler y se agrega a la funcionalidad del boton. --- src/components/FormLogin.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/FormLogin.tsx b/src/components/FormLogin.tsx index 8762e11..5be236c 100644 --- a/src/components/FormLogin.tsx +++ b/src/components/FormLogin.tsx @@ -1,19 +1,31 @@ import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { useLoginView } from '../hooks/useLoginView'; +import { useAuth } from '../hooks/useAuth'; import { Input } from './Input'; export function FormLogin() { + const navigate = useNavigate(); const { LOGIN_VIEW_STATE, setLoginFields } = useLoginView(); + const { login } = useAuth(); const [identifier, setIdentifier] = useState(LOGIN_VIEW_STATE.loginFields.identifier || ''); const [password, setPassword] = useState(''); + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + const success = await login(identifier, password); + if (success) { + navigate('/main/'); + } + }; + useEffect(() => { setLoginFields({ identifier }); }, [setLoginFields, identifier]); return ( -
+

Iniciar Sesión

From 028ea64d545eadd990b0dafb83d366a1b3ed3cd7 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 10:47:10 -0400 Subject: [PATCH 02/23] Se implementa una logica de proteccion de rutas en caso de que no se este logeado. Siempre redireccionara a la startpage. --- src/components/ProtectedRoute.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/components/ProtectedRoute.tsx diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx new file mode 100644 index 0000000..42850a5 --- /dev/null +++ b/src/components/ProtectedRoute.tsx @@ -0,0 +1,16 @@ +import { useNavigate } from "react-router-dom"; +import React, { useEffect } from "react"; +import { useAuth } from "../hooks/useAuth"; + +export function ProtectedRoute({ children }: { children: React.ReactNode }) { + const navigate = useNavigate(); + const { AUTH_STATE } = useAuth(); + + useEffect(() => { + if (AUTH_STATE.isAuthenticated === false) { + navigate("/"); + } + }, [AUTH_STATE.isAuthenticated, navigate]); + + return <>{children}; +}; \ No newline at end of file From b90ac7f8ba7d1ac39af80c7afb90769425bc9a63 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:00:45 -0400 Subject: [PATCH 03/23] Actualizacion de nombre el custom hook useAuthentication --- src/components/FormLogin.tsx | 4 ++-- src/components/ProtectedRoute.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/FormLogin.tsx b/src/components/FormLogin.tsx index 5be236c..6e55310 100644 --- a/src/components/FormLogin.tsx +++ b/src/components/FormLogin.tsx @@ -1,13 +1,13 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useLoginView } from '../hooks/useLoginView'; -import { useAuth } from '../hooks/useAuth'; +import { useAuthentication } from '../hooks/useAuthentication'; import { Input } from './Input'; export function FormLogin() { const navigate = useNavigate(); const { LOGIN_VIEW_STATE, setLoginFields } = useLoginView(); - const { login } = useAuth(); + const { login } = useAuthentication(); const [identifier, setIdentifier] = useState(LOGIN_VIEW_STATE.loginFields.identifier || ''); const [password, setPassword] = useState(''); diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx index 42850a5..57eb6c7 100644 --- a/src/components/ProtectedRoute.tsx +++ b/src/components/ProtectedRoute.tsx @@ -1,10 +1,10 @@ import { useNavigate } from "react-router-dom"; import React, { useEffect } from "react"; -import { useAuth } from "../hooks/useAuth"; +import { useAuthentication } from "../hooks/useAuthentication"; export function ProtectedRoute({ children }: { children: React.ReactNode }) { const navigate = useNavigate(); - const { AUTH_STATE } = useAuth(); + const { AUTH_STATE } = useAuthentication(); useEffect(() => { if (AUTH_STATE.isAuthenticated === false) { From e3ef8c23c7350ccf1ca74680d84887ab6145c877 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:01:28 -0400 Subject: [PATCH 04/23] Inclusion e instalacion de CORS para url de desarrollo --- backend/settings.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/settings.py b/backend/settings.py index 541cda4..66fced8 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -44,11 +44,13 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'rest_framework', + 'corsheaders', + 'rest_framework', 'api', ] MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -58,6 +60,10 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +CORS_ALLOWED_ORIGINS = [ + "http://localhost:5173", +] + REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', From 24b1d5f904daea3924e93d30e737c8d6acc1eb2c Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:02:56 -0400 Subject: [PATCH 05/23] Se incluye context para Authenticacion. Se modifica el nombre del archivo para LoginView --- src/contexts/AuthenticationContext.tsx | 4 ++++ src/contexts/{loginViewContext.tsx => LoginViewContext.tsx} | 0 2 files changed, 4 insertions(+) create mode 100644 src/contexts/AuthenticationContext.tsx rename src/contexts/{loginViewContext.tsx => LoginViewContext.tsx} (100%) diff --git a/src/contexts/AuthenticationContext.tsx b/src/contexts/AuthenticationContext.tsx new file mode 100644 index 0000000..b1a4946 --- /dev/null +++ b/src/contexts/AuthenticationContext.tsx @@ -0,0 +1,4 @@ +import { createContext } from 'react'; +import type { AuthContextType } from '../utils/types'; + +export const AuthenticationContext = createContext(undefined); \ No newline at end of file diff --git a/src/contexts/loginViewContext.tsx b/src/contexts/LoginViewContext.tsx similarity index 100% rename from src/contexts/loginViewContext.tsx rename to src/contexts/LoginViewContext.tsx From d68bdf651daf2853178389e2dc22aff6c82632d2 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:03:30 -0400 Subject: [PATCH 06/23] Actualizacion de nombre de context --- src/hooks/useLoginView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useLoginView.ts b/src/hooks/useLoginView.ts index cd4df3f..a097a8c 100644 --- a/src/hooks/useLoginView.ts +++ b/src/hooks/useLoginView.ts @@ -1,5 +1,5 @@ import { useContext } from 'react'; -import { LoginViewContext } from '../contexts/loginViewContext'; +import { LoginViewContext } from '../contexts/LoginViewContext'; export function useLoginView() { const context = useContext(LoginViewContext); From b86c6fc985eef5beed7213488c2356ead0e914ce Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:03:54 -0400 Subject: [PATCH 07/23] Se incluye custom hooks para uso de provider de authenticacion --- src/hooks/useAuthentication.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/hooks/useAuthentication.ts diff --git a/src/hooks/useAuthentication.ts b/src/hooks/useAuthentication.ts new file mode 100644 index 0000000..fde4a3f --- /dev/null +++ b/src/hooks/useAuthentication.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { AuthenticationContext } from '../contexts/AuthenticationContext'; + +export function useAuthentication() { + const context = useContext(AuthenticationContext); + if (!context) { + throw new Error('useAuthentication must be used within a AuthenticationProvider'); + } + return context; +} \ No newline at end of file From 5a3f6332baf5fa139b6f495eb4111c11c3810660 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:07:09 -0400 Subject: [PATCH 08/23] Se incluye pagina web principal con un titulo, texto de verificacion del usuario que ha logeado y un boton de logout. El logout aprovecha la funcion provista por el context authentication --- src/pages/MainPage.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/pages/MainPage.tsx diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx new file mode 100644 index 0000000..e0d94bd --- /dev/null +++ b/src/pages/MainPage.tsx @@ -0,0 +1,19 @@ +import { useNavigate } from 'react-router-dom'; +import { useAuthentication } from '../hooks/useAuthentication'; + +export function MainPage() { + const navigate = useNavigate(); + const { AUTH_STATE, logout } = useAuthentication(); + + const handleLogout = () => { + logout(); + navigate('/'); + } + return ( +
+

Main Page

+

Welcome, {AUTH_STATE.user ? AUTH_STATE.user.username : 'Guest'}!

+ +
+ ) +} \ No newline at end of file From 735890f491804425ec4b51d40f533f7cb750c018 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:27:43 -0400 Subject: [PATCH 09/23] Actualizacion general de nombre de archivos. Cambio de sitio para logica de guardado en localstorage. Actualizacion de tipos. --- src/providers/LoginViewProvider.tsx | 21 +++++++++++++---- .../{loginView.ts => loginViewReducer.ts} | 23 ++++--------------- 2 files changed, 21 insertions(+), 23 deletions(-) rename src/reducers/{loginView.ts => loginViewReducer.ts} (77%) diff --git a/src/providers/LoginViewProvider.tsx b/src/providers/LoginViewProvider.tsx index 53ef307..7e4b14d 100644 --- a/src/providers/LoginViewProvider.tsx +++ b/src/providers/LoginViewProvider.tsx @@ -1,7 +1,12 @@ import React, { useCallback } from 'react'; -import { loginViewReducer, loginViewState } from '../reducers/loginView'; -import { LoginViewContext } from '../contexts/loginViewContext'; -import type { Register, Login } from '../utils/types'; +import { loginViewReducer, loginViewState } from '../reducers/loginViewReducer'; +import { LoginViewContext } from '../contexts/LoginViewContext'; +import type { RegisterType, LoginType } from '../utils/types'; +import type { LoginViewStateType } from '../utils/types'; + +function updateLocalStorageLoginView(state: LoginViewStateType) { + localStorage.setItem('loginViewState', JSON.stringify(state)); +}; function useLoginViewReducer() { const [state, dispatch] = React.useReducer(loginViewReducer, loginViewState); @@ -10,7 +15,7 @@ function useLoginViewReducer() { dispatch({ type: 'SET_LOGIN_VIEW', payload: state.registerFields }); }, [state.registerFields]); - const setLoginFields = useCallback((loginFields: Login) => { + const setLoginFields = useCallback((loginFields: LoginType) => { dispatch({ type: 'SET_LOGIN_FIELDS', payload: loginFields }); }, []); @@ -18,7 +23,7 @@ function useLoginViewReducer() { dispatch({ type: 'SET_REGISTER_VIEW', payload: state.loginFields }); }, [state.loginFields]); - const setRegisterFields = useCallback((registerFields: Register) => { + const setRegisterFields = useCallback((registerFields: RegisterType) => { dispatch({ type: 'SET_REGISTER_FIELDS', payload: registerFields }); }, []); @@ -32,6 +37,12 @@ function useLoginViewReducer() { export function LoginViewProvider({ children }: { children: React.ReactNode }) { const { state, showLogin, showRegister, setLoginFields, setRegisterFields, clearFields } = useLoginViewReducer(); + // Sincroniza localStorage cada vez que el estado cambia + /*React.useEffect(() => { + updateLocalStorageLoginView(state); + }, [state]); + */ + return ( {children} diff --git a/src/reducers/loginView.ts b/src/reducers/loginViewReducer.ts similarity index 77% rename from src/reducers/loginView.ts rename to src/reducers/loginViewReducer.ts index 8f76099..45e5e94 100644 --- a/src/reducers/loginView.ts +++ b/src/reducers/loginViewReducer.ts @@ -1,6 +1,6 @@ -import type { ViewType, LoginViewState, Action } from '../utils/types'; +import type { ViewType, LoginViewStateType, LoginViewActionType } from '../utils/types'; -export const loginViewState: LoginViewState = (() => { +export const loginViewState: LoginViewStateType = (() => { const stored = localStorage.getItem('loginViewState'); if (stored) { try { @@ -28,22 +28,10 @@ export const loginViewState: LoginViewState = (() => { }; })(); -export function updateLocalStorageLoginView(state: LoginViewState) { - localStorage.setItem('loginViewState', JSON.stringify(state)); -}; - -export const STATE_ACTION_TYPES = { - SET_LOGIN_VIEW: 'SET_LOGIN_VIEW', - SET_LOGIN_FIELDS: 'SET_LOGIN_FIELDS', - SET_REGISTER_VIEW: 'SET_REGISTER_VIEW', - SET_REGISTER_FIELDS: 'SET_REGISTER_FIELDS', - RESET_FIELDS: 'RESET_FIELDS', -}; - export function loginViewReducer( - state: LoginViewState = loginViewState, - action: Action -): LoginViewState { + state: LoginViewStateType = loginViewState, + action: LoginViewActionType +): LoginViewStateType { if (!action || !action.type) return state; let newState = state; @@ -89,6 +77,5 @@ export function loginViewReducer( }; } - //updateLocalStorageLoginView(newState); return newState; }; \ No newline at end of file From 476f22a3829c7f303a9f8640955f5ee84a1e4dfa Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:28:59 -0400 Subject: [PATCH 10/23] Actualizacion de tipos relacionado con LoginView y sus acciones. Inclusion de tipos y acciones para Authentication context, provider, state --- src/utils/types.ts | 56 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/src/utils/types.ts b/src/utils/types.ts index 1002ccb..dd98012 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,33 +1,61 @@ export type ViewType = 'login' | 'register'; -export type Register = { +export type RegisterType = { full_name: string; username: string; email: string; }; -export type Login = { +export type LoginType = { identifier: string; }; -export type LoginViewState = { +export type LoginViewStateType = { view: ViewType; - loginFields: Login; - registerFields: Register; + loginFields: LoginType; + registerFields: RegisterType; }; export type LoginViewContextType = { - LOGIN_VIEW_STATE: LoginViewState; + LOGIN_VIEW_STATE: LoginViewStateType; showLogin: () => void; - setLoginFields: (loginFields: Login) => void; + setLoginFields: (loginFields: LoginType) => void; showRegister: () => void; - setRegisterFields: (registerFields: Register) => void; + setRegisterFields: (registerFields: RegisterType) => void; clearFields: () => void; }; -export type Action = - | { type: 'SET_LOGIN_VIEW'; payload: Register } - | { type: 'SET_LOGIN_FIELDS'; payload: Login } - | { type: 'SET_REGISTER_VIEW'; payload: Login } - | { type: 'SET_REGISTER_FIELDS'; payload: Register } - | { type: 'RESET_FIELDS' }; \ No newline at end of file +export type LoginViewActionType = + | { type: 'SET_LOGIN_VIEW'; payload: RegisterType } + | { type: 'SET_LOGIN_FIELDS'; payload: LoginType } + | { type: 'SET_REGISTER_VIEW'; payload: LoginType } + | { type: 'SET_REGISTER_FIELDS'; payload: RegisterType } + | { type: 'RESET_FIELDS' }; + + +export type UserType = { + id: number; + username: string; + email: string; + full_name: string; + role: string; +}; + +export type AuthStateType = { + user: UserType | null; + tk_access: string | null; + tk_refresh: string | null; + isAuthenticated: boolean; + failedAttempts: number; +}; + +export type AuthContextType = { + AUTH_STATE: AuthStateType; + login: (id: string, pass: string) => Promise; + logout: () => void; +}; + +export type AuthActionType = + | { type: 'LOGIN_SUCCESS'; payload: { user: UserType; access: string; refresh: string } } + | { type: 'LOGIN_FAILURE' } + | { type: 'LOGOUT' }; \ No newline at end of file From e6893f62f9852e584706769ea8ffeb23b34a2574 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:30:24 -0400 Subject: [PATCH 11/23] Se implementa React Router para navegacion entre componentes. Se protege la ruta main en caso de no haber login activo. Se incluye la pagina principal --- src/App.tsx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ea4ffb9..97a08c8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,12 +1,24 @@ import './styles/App.css' +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { ProtectedRoute } from './components/ProtectedRoute'; import { StartPage } from './pages/StartPage'; +import { MainPage } from './pages/MainPage'; import { Footer } from './components/Footer'; export function App() { - return ( -
- -
-
+ return ( + +
+ + } /> + + + + } /> + +
+
+
); }; \ No newline at end of file From 7222008b2db388fefaafe688d2a014c2877a57e3 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:30:56 -0400 Subject: [PATCH 12/23] Se aplica provider de authenticacion a toda la aplicacion. --- src/main.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main.tsx b/src/main.tsx index 34dbef9..af517d3 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,9 +2,12 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './styles/index.css' import { App } from './App.tsx' +import { AuthenticationProvider } from './providers/AuthenticationProvider.tsx' createRoot(document.getElementById('root')!).render( - - - , + + + + + , ) From f60ffac42a3173208bebb52daf024d2a2b113cda Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:31:42 -0400 Subject: [PATCH 13/23] Instalacion de react router dom para uso de React Router --- package-lock.json | 64 +++++++++++++++++++++++++++++++++++++++++++---- package.json | 3 ++- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index a30e96b..50f3d2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,25 +9,26 @@ "version": "0.0.0", "dependencies": { "react": "19.1.1", - "react-dom": "19.1.1" + "react-dom": "19.1.1", + "react-router-dom": "^7.9.3" }, "devDependencies": { "@eslint/js": "9.35.0", "@testing-library/jest-dom": "6.8.0", "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", - "@types/jest": "^30.0.0", + "@types/jest": "30.0.0", "@types/react": "19.1.13", "@types/react-dom": "19.1.9", "@vitejs/plugin-react-swc": "4.0.1", - "@vitest/coverage-v8": "^3.2.4", - "@vitest/ui": "^3.2.4", + "@vitest/coverage-v8": "3.2.4", + "@vitest/ui": "3.2.4", "eslint": "9.35.0", "eslint-plugin-react-hooks": "5.2.0", "eslint-plugin-react-refresh": "0.4.20", "gh-pages": "6.3.0", "globals": "16.4.0", - "jsdom": "^27.0.0", + "jsdom": "27.0.0", "typescript": "5.8.3", "typescript-eslint": "8.43.0", "vite": "7.1.6", @@ -2730,6 +2731,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4727,6 +4737,44 @@ "license": "MIT", "peer": true }, + "node_modules/react-router": { + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz", + "integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.3.tgz", + "integrity": "sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==", + "license": "MIT", + "dependencies": { + "react-router": "7.9.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -4881,6 +4929,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index fbc8e29..d781266 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ }, "dependencies": { "react": "19.1.1", - "react-dom": "19.1.1" + "react-dom": "19.1.1", + "react-router-dom": "7.9.3" }, "devDependencies": { "@eslint/js": "9.35.0", From 2aeb75b83e6a5d8b74b0c9bc27014a9a80bb582c Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:32:00 -0400 Subject: [PATCH 14/23] Instalacion de CORS --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 7102b05..679135c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ asgiref==3.9.1 Django==5.2.6 +django-cors-headers==4.9.0 djangorestframework==3.16.1 djangorestframework_simplejwt==5.5.1 gunicorn==23.0.0 From cb2ae3c6d99d717541abcbf097916a7e9a532ba1 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:32:41 -0400 Subject: [PATCH 15/23] Inclusion de codigo comentado para el uso de prueba local de peticiones desde localhost usando proxy --- vite.config.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index 88ad137..ba2bb6c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,4 +20,14 @@ export default defineConfig({ ], }, }, -}) \ No newline at end of file +}) +/* +server: { + proxy: { + '/api': { + target: 'https://backend-9tcm.onrender.com', + changeOrigin: true, + secure: false, + }, + } +} */ \ No newline at end of file From 7d28e9d05a4237bd670f9114c53d40f26b861acb Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:34:16 -0400 Subject: [PATCH 16/23] Implementacion de reducer y provider para authenticacion de usuarios. Se cuenta con 3 acciones clave, login que guardan el token JWT de acceso y refresco junto a la informacion del usuario. Logout resetea el estado. Login failed actualiza los intentos fallidos de login. --- src/providers/AuthenticationProvider.tsx | 55 ++++++++++++++++++++++ src/reducers/authenticationReducer.ts | 58 ++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/providers/AuthenticationProvider.tsx create mode 100644 src/reducers/authenticationReducer.ts diff --git a/src/providers/AuthenticationProvider.tsx b/src/providers/AuthenticationProvider.tsx new file mode 100644 index 0000000..94c2502 --- /dev/null +++ b/src/providers/AuthenticationProvider.tsx @@ -0,0 +1,55 @@ +import React, { useCallback, useEffect, useReducer } from 'react'; +import { authenticationReducer, AuthState } from '../reducers/authenticationReducer'; +import { AuthenticationContext } from '../contexts/AuthenticationContext'; +import type { AuthStateType } from '../utils/types'; + +function updateLocalStorageAuth(state: AuthStateType) { + localStorage.setItem('authState', JSON.stringify(state)); +}; + +function useAuthReducer() { + const [state, dispatch] = useReducer(authenticationReducer, AuthState); + + const login = useCallback(async (identifier: string, password: string) => { + try { + //const res = await fetch('/api/login/', { + const res = await fetch('https://backend-9tcm.onrender.com/api/login/', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ identifier, password }), + }); + const data = await res.json(); + if (res.ok && data.success) { + dispatch({ type: 'LOGIN_SUCCESS', payload: { user: data.user, access: data.access, refresh: data.refresh } }); + return true; + } else { + dispatch({ type: 'LOGIN_FAILURE' }); + return false; + } + } catch { + dispatch({ type: 'LOGIN_FAILURE' }); + return false; + } + }, []); + + const logout = useCallback(() => { + dispatch({ type: 'LOGOUT' }); + }, []); + + return { state, login, logout }; +} + +export function AuthenticationProvider({ children }: { children: React.ReactNode }) { + const { state, login, logout } = useAuthReducer(); + + // Sincroniza localStorage cada vez que el estado cambia + useEffect(() => { + updateLocalStorageAuth(state); + }, [state]); + + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/src/reducers/authenticationReducer.ts b/src/reducers/authenticationReducer.ts new file mode 100644 index 0000000..08221ca --- /dev/null +++ b/src/reducers/authenticationReducer.ts @@ -0,0 +1,58 @@ +import type { AuthActionType, AuthStateType } from '../utils/types'; + +export const AuthState: AuthStateType = (() => { + const stored = localStorage.getItem('authState'); + if (stored) { + try { + const parsed = JSON.parse(stored); + // Ensure all fields exist + return { + user: parsed.user || null, + tk_access: parsed.tk_access || null, + tk_refresh: parsed.tk_refresh || null, + isAuthenticated: !!parsed.tk_access, + failedAttempts: parsed.failedAttempts || 0, + }; + } catch { + // fallback to default + } + } + return { + user: null, + tk_access: null, + tk_refresh: null, + isAuthenticated: false, + failedAttempts: 0, + }; +})(); + +export function authenticationReducer( + state: AuthStateType = AuthState, + action: AuthActionType +): AuthStateType { + switch (action.type) { + case 'LOGIN_SUCCESS': + return { + ...state, + user: action.payload.user, + tk_access: action.payload.access, + tk_refresh: action.payload.refresh, + isAuthenticated: true, + }; + case 'LOGIN_FAILURE': + return { + ...state, + failedAttempts: state.failedAttempts + 1, + }; + case 'LOGOUT': + return { + user: null, + tk_access: null, + tk_refresh: null, + isAuthenticated: false, + failedAttempts: 0, + }; + default: + return state; + } +} \ No newline at end of file From d8f142585f323a63854d285d54366fa20c50528c Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 11:53:53 -0400 Subject: [PATCH 17/23] Se implementa handler de registro que de ser exitoso se reset estado de login view y se redireciona a la vista de login. Falta manejar errores de registro y en caso de error de peticion --- src/components/FormRegister.tsx | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/components/FormRegister.tsx b/src/components/FormRegister.tsx index f2cd3b0..68d16fb 100644 --- a/src/components/FormRegister.tsx +++ b/src/components/FormRegister.tsx @@ -3,7 +3,7 @@ import { useLoginView } from '../hooks/useLoginView'; import { Input } from './Input'; export function FormRegister() { - const { LOGIN_VIEW_STATE, setRegisterFields } = useLoginView(); + const { LOGIN_VIEW_STATE, showLogin, setRegisterFields, clearFields } = useLoginView(); const [full_name, setFullName] = useState(LOGIN_VIEW_STATE.registerFields.full_name); const [username, setUsername] = useState(LOGIN_VIEW_STATE.registerFields.username); @@ -19,6 +19,32 @@ export function FormRegister() { setRegisterFields({ full_name, username, email }); }, [setRegisterFields, full_name, username, email]); + async function handleRegister(e: React.FormEvent) { + if (emailError || passwordError) return; + + e.preventDefault(); + + try { + //const res = await fetch('/api/users/', { + const res = await fetch('https://backend-9tcm.onrender.com/api/users/', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ full_name, username, email, password }), + }); + const data = await res.json(); + if (res.ok && data.success) { + clearFields(); + // Registro exitoso, redirigir o mostrar mensaje + showLogin(); + + } else { + // Manejar error de registro + } + } catch { + // Manejar error de peticion + } + } + function handleEmailBlur() { if (email && confirmEmail && email !== confirmEmail) { setEmailError(true); @@ -52,7 +78,7 @@ export function FormRegister() { } return ( - +

Registrarse

From c96f67be0f63971d0419dd4f4ed313ece09c0766 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 12:09:49 -0400 Subject: [PATCH 18/23] Se envuelve cada tests en su respectivo authentication provider y Memoryrauter para solucion a error de useNavigate --- src/components/FormLogin.test.tsx | 12 +++++++++--- src/components/LoginRegisterPanel.test.tsx | 12 +++++++++--- src/pages/StartPage.test.tsx | 19 +++++++++++++++---- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/components/FormLogin.test.tsx b/src/components/FormLogin.test.tsx index 2552c98..7745a0b 100644 --- a/src/components/FormLogin.test.tsx +++ b/src/components/FormLogin.test.tsx @@ -1,13 +1,19 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { FormLogin } from './FormLogin'; import { LoginViewProvider } from '../providers/LoginViewProvider'; +import { AuthenticationProvider } from '../providers/AuthenticationProvider'; +import { MemoryRouter } from 'react-router-dom'; describe('FormLogin component', () => { function renderWithProvider() { return render( - - - + + + + + + + ); } diff --git a/src/components/LoginRegisterPanel.test.tsx b/src/components/LoginRegisterPanel.test.tsx index db69ae7..580f2f3 100644 --- a/src/components/LoginRegisterPanel.test.tsx +++ b/src/components/LoginRegisterPanel.test.tsx @@ -1,13 +1,19 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { LoginRegisterPanel } from './LoginRegisterPanel'; import { LoginViewProvider } from '../providers/LoginViewProvider'; +import { AuthenticationProvider } from '../providers/AuthenticationProvider'; +import { MemoryRouter } from 'react-router-dom'; describe('LoginRegisterPanel component', () => { function renderWithProvider() { return render( - - - + + + + + + + ); } diff --git a/src/pages/StartPage.test.tsx b/src/pages/StartPage.test.tsx index c4bb1ee..1c104fb 100644 --- a/src/pages/StartPage.test.tsx +++ b/src/pages/StartPage.test.tsx @@ -1,22 +1,33 @@ import { render, screen } from '@testing-library/react'; import { StartPage } from './StartPage'; +import { AuthenticationProvider } from '../providers/AuthenticationProvider'; +import { MemoryRouter } from 'react-router-dom'; describe('StartPage component', () => { + function renderWithProviders() { + return render( + + + + + + ); + } + test('renders logo image with correct alt text', () => { - render(); + renderWithProviders(); const logo = screen.getByAltText(/Logotipo de ClonTube, meme de peter parker con logo de youtube/i); expect(logo).toBeInTheDocument(); expect(logo).toHaveAttribute('src', './logo.png'); }); test('renders ClonTube heading', () => { - render(); + renderWithProviders(); expect(screen.getByRole('heading', { name: /ClonTube/i, level: 2 })).toBeInTheDocument(); }); test('renders LoginRegisterPanel', () => { - render(); - // Busca un texto característico del panel de login/register + renderWithProviders(); expect(screen.getByText(/Iniciar Sesión/i)).toBeInTheDocument(); }); }); \ No newline at end of file From 08a13d3fb626a918e5839f49b5e88a9bee37d776 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 12:11:30 -0400 Subject: [PATCH 19/23] Se comenta que cors debe ir siempre primero en la lista de middleware --- backend/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/settings.py b/backend/settings.py index 66fced8..8847050 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -50,7 +50,7 @@ ] MIDDLEWARE = [ - 'corsheaders.middleware.CorsMiddleware', + 'corsheaders.middleware.CorsMiddleware', # debe ir primero 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', From 1fea3e3ac3dc74f14ebd401325b0bca65f239330 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 12:12:05 -0400 Subject: [PATCH 20/23] tab por spaces --- backend/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/settings.py b/backend/settings.py index 8847050..278a871 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -44,7 +44,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'corsheaders', + 'corsheaders', 'rest_framework', 'api', ] From c9dcaa6ccee9f071f87ebbd8e344097e0925ecbe Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 12:13:38 -0400 Subject: [PATCH 21/23] Manejo de catch por consola --- src/providers/AuthenticationProvider.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/providers/AuthenticationProvider.tsx b/src/providers/AuthenticationProvider.tsx index 94c2502..e4f36eb 100644 --- a/src/providers/AuthenticationProvider.tsx +++ b/src/providers/AuthenticationProvider.tsx @@ -26,7 +26,8 @@ function useAuthReducer() { dispatch({ type: 'LOGIN_FAILURE' }); return false; } - } catch { + } catch (error) { + console.error('Login error:', error); dispatch({ type: 'LOGIN_FAILURE' }); return false; } From 6baee639bf8715441eca4434de70496008e7d448 Mon Sep 17 00:00:00 2001 From: JMLTUnderCode Date: Sat, 27 Sep 2025 12:14:42 -0400 Subject: [PATCH 22/23] Eliminacion de comentario de url antigua --- src/providers/AuthenticationProvider.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/providers/AuthenticationProvider.tsx b/src/providers/AuthenticationProvider.tsx index e4f36eb..7e0ec19 100644 --- a/src/providers/AuthenticationProvider.tsx +++ b/src/providers/AuthenticationProvider.tsx @@ -12,7 +12,6 @@ function useAuthReducer() { const login = useCallback(async (identifier: string, password: string) => { try { - //const res = await fetch('/api/login/', { const res = await fetch('https://backend-9tcm.onrender.com/api/login/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, From 2a259c91a2b2b9cf36109692469151e6056f1707 Mon Sep 17 00:00:00 2001 From: Junior Miguel Lara Torres <45317638+JMLTUnderCode@users.noreply.github.com> Date: Sat, 27 Sep 2025 12:18:33 -0400 Subject: [PATCH 23/23] Elimnacion de comentario Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/components/FormRegister.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/FormRegister.tsx b/src/components/FormRegister.tsx index 68d16fb..f19714d 100644 --- a/src/components/FormRegister.tsx +++ b/src/components/FormRegister.tsx @@ -25,7 +25,6 @@ export function FormRegister() { e.preventDefault(); try { - //const res = await fetch('/api/users/', { const res = await fetch('https://backend-9tcm.onrender.com/api/users/', { method: 'POST', headers: { 'Content-Type': 'application/json' },