test
This commit is contained in:
13
src/App.jsx
Normal file
13
src/App.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
|
||||
import { Authenticated } from "./components";
|
||||
|
||||
import { Router } from "./router";
|
||||
|
||||
const App = () => (
|
||||
<Authenticated>
|
||||
<Router />
|
||||
</Authenticated>
|
||||
);
|
||||
|
||||
export default App;
|
30
src/api/authentication.js
Normal file
30
src/api/authentication.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import axios from "axios";
|
||||
|
||||
export const isLoggedIn = async () => {
|
||||
try {
|
||||
const response = await axios.get("/authenticate");
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return error.response.data;
|
||||
}
|
||||
};
|
||||
|
||||
export const login = async (username, password) => {
|
||||
try {
|
||||
const response = await axios.post("/authenticate", { username, password });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return error.response.data;
|
||||
}
|
||||
};
|
||||
|
||||
export const logout = async () => {
|
||||
try {
|
||||
const response = await axios.delete("/authenticate");
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return error.response.data;
|
||||
}
|
||||
};
|
2
src/api/index.js
Normal file
2
src/api/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './authentication'
|
||||
export * from './user'
|
14
src/api/user.js
Normal file
14
src/api/user.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import axios from "axios";
|
||||
|
||||
export const createUser = async (username, password, confirmation) => {
|
||||
try {
|
||||
const response = await axios.post("/user", {
|
||||
username,
|
||||
password,
|
||||
confirmation,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return error.response.data;
|
||||
}
|
||||
};
|
0
src/assets/styles/index.css
Normal file
0
src/assets/styles/index.css
Normal file
7
src/components/authenticated.jsx
Normal file
7
src/components/authenticated.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { AuthenticationProvider } from "../contexts";
|
||||
|
||||
export const Authenticated = ({ children }) => {
|
||||
return <AuthenticationProvider>{children}</AuthenticationProvider>;
|
||||
};
|
1
src/components/index.js
Normal file
1
src/components/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./authenticated";
|
98
src/contexts/auth-context.jsx
Normal file
98
src/contexts/auth-context.jsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
createUser as registerApi,
|
||||
isLoggedIn,
|
||||
login as loginApi,
|
||||
logout as logoutApi,
|
||||
} from "../api";
|
||||
import { useQuery } from "../hooks";
|
||||
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
const initState = {
|
||||
user: undefined,
|
||||
};
|
||||
|
||||
export const AuthenticationContext = React.createContext({
|
||||
...initState,
|
||||
login: () => {},
|
||||
register: () => {},
|
||||
logout: () => {},
|
||||
});
|
||||
|
||||
export const AuthenticationProvider = ({ children }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const query = useQuery();
|
||||
const [authState, setAuthState] = useState(initState);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const redirect = () => {
|
||||
navigate(query.get("redirect_uri") || "/");
|
||||
};
|
||||
|
||||
const login = async (email, password) => {
|
||||
loginApi(email, password)
|
||||
.then((user) => {
|
||||
setAuthState({ user });
|
||||
redirect();
|
||||
})
|
||||
.catch(() => {
|
||||
setAuthState({ user: undefined });
|
||||
});
|
||||
};
|
||||
|
||||
const register = async (username, password, confirmation) => {
|
||||
registerApi(username, password, confirmation).then((user) => {
|
||||
setAuthState({ user });
|
||||
redirect();
|
||||
});
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
logoutApi().then(() => {
|
||||
setAuthState(initState);
|
||||
navigate(`/login`);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
isLoggedIn()
|
||||
.then((user) => {
|
||||
if (user === "Unauthorized") throw new Error("Unauthorized");
|
||||
setAuthState({ user });
|
||||
if (location.pathname === "/login") {
|
||||
redirect();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setAuthState({ user: undefined });
|
||||
|
||||
if (!location.pathname.match(/^(\/|\/login|\/register)$/)) {
|
||||
navigate(`/login?redirect_uri=${encodeURI(location.pathname)}`);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (location.pathname === "/logout") {
|
||||
logout();
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
if (isLoading) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthenticationContext.Provider
|
||||
value={{ ...authState, login, logout, register }}
|
||||
>
|
||||
{children}
|
||||
</AuthenticationContext.Provider>
|
||||
);
|
||||
};
|
1
src/contexts/index.js
Normal file
1
src/contexts/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './auth-context'
|
2
src/hooks/index.js
Normal file
2
src/hooks/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./use-auth";
|
||||
export * from "./use-query";
|
7
src/hooks/use-auth.js
Normal file
7
src/hooks/use-auth.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { AuthenticationContext } from "../contexts";
|
||||
|
||||
export function useAuth() {
|
||||
return React.useContext(AuthenticationContext);
|
||||
}
|
9
src/hooks/use-query.js
Normal file
9
src/hooks/use-query.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
export const useQuery = () => {
|
||||
const { search } = useLocation()
|
||||
|
||||
return React.useMemo(() => new URLSearchParams(search), [search])
|
||||
}
|
19
src/main.jsx
Normal file
19
src/main.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./app.jsx";
|
||||
import "./assets/styles/index.css";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import setupAxios from "./setupAxios";
|
||||
|
||||
setupAxios();
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<CookiesProvider>
|
||||
<App />
|
||||
</CookiesProvider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
);
|
2
src/pages/authenticated/index.js
Normal file
2
src/pages/authenticated/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './login'
|
||||
export * from './register'
|
42
src/pages/authenticated/login.jsx
Normal file
42
src/pages/authenticated/login.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useAuth } from "../../hooks";
|
||||
|
||||
export const Login = () => {
|
||||
const { login } = useAuth();
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState();
|
||||
|
||||
const onSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const response = await login(username, password);
|
||||
|
||||
if (response && !response.success) {
|
||||
setError(response.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Login page</h1>
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<form onSubmit={onSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
placeholder="username"
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder="password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
49
src/pages/authenticated/register.jsx
Normal file
49
src/pages/authenticated/register.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useAuth } from "../../hooks";
|
||||
|
||||
export const Register = () => {
|
||||
const { register } = useAuth();
|
||||
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmation, setConfirmation] = useState("");
|
||||
const [error, setError] = useState();
|
||||
|
||||
const onSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const response = await register(username, password, confirmation);
|
||||
|
||||
if (response && !response.success) {
|
||||
setError(response.error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Register page</h1>
|
||||
{error && <p className="text-red-500">{error}</p>}
|
||||
<form onSubmit={onSubmit}>
|
||||
<input
|
||||
type="text"
|
||||
value={username}
|
||||
placeholder="username"
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder="password"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={confirmation}
|
||||
placeholder="confirmation"
|
||||
onChange={(e) => setConfirmation(e.target.value)}
|
||||
/>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
14
src/pages/home.jsx
Normal file
14
src/pages/home.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import { useAuth } from "../hooks";
|
||||
|
||||
export const Home = () => {
|
||||
const { user } = useAuth();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Home page</h1>
|
||||
{user && <h2>Hello {user.user.username}</h2>}
|
||||
</div>
|
||||
);
|
||||
};
|
2
src/pages/index.js
Normal file
2
src/pages/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./authenticated";
|
||||
export * from "./home";
|
12
src/router.jsx
Normal file
12
src/router.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
|
||||
import { Home, Login, Register } from "./pages";
|
||||
|
||||
export const Router = () => (
|
||||
<Routes>
|
||||
<Route index element={<Home />} />
|
||||
<Route path="login" element={<Login />} />
|
||||
<Route path="register" element={<Register />} />
|
||||
</Routes>
|
||||
);
|
8
src/setupAxios.js
Normal file
8
src/setupAxios.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import axios from "axios";
|
||||
|
||||
const setupAxios = () => {
|
||||
axios.defaults.baseURL = import.meta.env.VITE_API_URL;
|
||||
axios.defaults.withCredentials = true;
|
||||
};
|
||||
|
||||
export default setupAxios;
|
Reference in New Issue
Block a user