diff --git a/client/package.json b/client/package.json
index c9f6fa5..748e19a 100644
--- a/client/package.json
+++ b/client/package.json
@@ -7,6 +7,7 @@
"@emotion/styled": "^11.9.3",
"@fontsource/roboto": "^4.5.7",
"@mui/icons-material": "^5.8.4",
+ "@mui/lab": "^5.0.0-alpha.89",
"@mui/material": "^5.8.7",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
@@ -17,6 +18,7 @@
"@types/react-dom": "^18.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-hook-form": "^7.33.1",
"react-scripts": "5.0.1",
"typescript": "^4.4.2",
"web-vitals": "^2.1.0"
diff --git a/client/src/App.tsx b/client/src/App.tsx
index af33dba..deb4e0d 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -9,6 +9,8 @@ import { AuthProvider } from "./providers/AuthProvider";
import "./index.css";
import { LoginDialog } from "./components/LoginDialog";
+import { UnautorizedBlock } from "./components/UnautorizedBlock";
+import { NewAccount } from "./pages/NewAccount/NewAccount";
function App() {
return (
@@ -16,6 +18,10 @@ function App() {
+ }
+ unauthenticatedContent={}
+ />
diff --git a/client/src/components/LoginDialog.tsx b/client/src/components/LoginDialog.tsx
index edc08d9..491de2d 100644
--- a/client/src/components/LoginDialog.tsx
+++ b/client/src/components/LoginDialog.tsx
@@ -7,11 +7,11 @@ import {
TextField,
} from "@mui/material";
import { useState } from "react";
-import { useAuth } from "../providers/AuthProvider";
+import { useAuth } from "../hooks/useAuth";
export const LoginDialog = () => {
const { isLoginDialogOpen, setIsLoginDialogOpen, login } = useAuth();
- console.log(isLoginDialogOpen);
+
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
diff --git a/client/src/components/Topbar.tsx b/client/src/components/Topbar.tsx
index 29aea0c..197d91c 100644
--- a/client/src/components/Topbar.tsx
+++ b/client/src/components/Topbar.tsx
@@ -1,5 +1,5 @@
import { AppBar, Button, Toolbar, Typography } from "@mui/material";
-import { useAuth } from "../providers/AuthProvider";
+import { useAuth } from "../hooks/useAuth";
export const Topbar = () => {
const { authenticated, logout, setIsLoginDialogOpen } = useAuth();
diff --git a/client/src/components/UnautorizedBlock.tsx b/client/src/components/UnautorizedBlock.tsx
new file mode 100644
index 0000000..d7762df
--- /dev/null
+++ b/client/src/components/UnautorizedBlock.tsx
@@ -0,0 +1,14 @@
+import { useAuth } from "../hooks/useAuth";
+
+export type UnautorizedBlockProps = {
+ protectedContent: React.ReactNode;
+ unauthenticatedContent: React.ReactNode;
+};
+
+export const UnautorizedBlock = (props: UnautorizedBlockProps) => {
+ const { authenticated } = useAuth();
+
+ return (
+ <>{authenticated ? props.protectedContent : props.unauthenticatedContent}>
+ );
+};
diff --git a/client/src/hooks/useAuth.ts b/client/src/hooks/useAuth.ts
new file mode 100644
index 0000000..a16fb5e
--- /dev/null
+++ b/client/src/hooks/useAuth.ts
@@ -0,0 +1,12 @@
+import { useContext } from "react";
+import { AuthContext } from "../providers/AuthProvider";
+
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+
+ if (context === null) {
+ throw new Error("You probably forgot to put .");
+ }
+
+ return context;
+};
diff --git a/client/src/pages/NewAccount/NewAccount.tsx b/client/src/pages/NewAccount/NewAccount.tsx
new file mode 100644
index 0000000..203a124
--- /dev/null
+++ b/client/src/pages/NewAccount/NewAccount.tsx
@@ -0,0 +1,84 @@
+import { LoadingButton } from "@mui/lab";
+import { Alert, Stack, TextField } from "@mui/material";
+import { useState } from "react";
+import { SubmitHandler, useForm } from "react-hook-form";
+import { useAuth } from "../../hooks/useAuth";
+import { createApiClient } from "../../utils/apiFetch";
+
+type NewAccountForm = {
+ email: string;
+ password: string;
+};
+
+export const NewAccount = () => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const apiClient = createApiClient();
+
+ const { login } = useAuth();
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ } = useForm();
+
+ const onSubmit: SubmitHandler = (data) => {
+ setLoading(true);
+
+ apiClient("users", {
+ method: "POST",
+ body: JSON.stringify(data),
+ })
+ .then(async (res) => {
+ const result = await res.json();
+ if (result.error) {
+ setError(result.error);
+ } else {
+ login(data.email, data.password);
+ }
+ })
+ .finally(() => setLoading(false));
+ };
+ return (
+
+
New Account Page
+ {error &&
{error}}
+
+
+ );
+};
diff --git a/client/src/providers/AuthProvider.tsx b/client/src/providers/AuthProvider.tsx
index 75f206d..c3c8c8b 100644
--- a/client/src/providers/AuthProvider.tsx
+++ b/client/src/providers/AuthProvider.tsx
@@ -1,10 +1,5 @@
-import {
- createContext,
- useCallback,
- useContext,
- useMemo,
- useState,
-} from "react";
+import { createContext, useCallback, useMemo, useState } from "react";
+import { createApiClient } from "../utils/apiFetch";
type LoginCallback = (email: string, password: string) => void;
type LogoutCallback = () => void;
@@ -16,19 +11,10 @@ type AuthProviderValue = {
logout: LogoutCallback;
setIsLoginDialogOpen: React.Dispatch>;
isLoginDialogOpen: boolean;
+ apiClient: typeof fetch;
};
-const AuthContext = createContext(null);
-
-export const useAuth = () => {
- const context = useContext(AuthContext);
-
- if (context === null) {
- throw new Error("You probably forgot to put .");
- }
-
- return context;
-};
+export const AuthContext = createContext(null);
type AuthProviderProps = {
children: React.ReactNode;
@@ -36,16 +22,29 @@ type AuthProviderProps = {
export const AuthProvider = ({ children, ...rest }: AuthProviderProps) => {
const [loading] = useState(true);
- const [token] = useState(null);
+ const [token, setToken] = useState(null);
const [isLoginDialogOpen, setIsLoginDialogOpen] = useState(false);
+ const apiClient = createApiClient(token ?? "");
- const login = useCallback((_email, __password) => {
- throw new Error("Not implemented yet");
- }, []);
+ const login = useCallback((email, password) => {
+ apiClient("users/sign_in", {
+ method: "POST",
+ body: JSON.stringify({
+ email,
+ password,
+ }),
+ }).then(async (res) => {
+ setToken(await res.json());
+ });
+ }, [apiClient, setToken]);
const logout = useCallback(() => {
- throw new Error("Not implemented yet");
- }, []);
+ apiClient("users/sign_out", {
+ method: "DELETE",
+ }).then(() => {
+ setToken(null);
+ });
+ }, [apiClient, setToken]);
const authenticated = !!token;
@@ -65,6 +64,7 @@ export const AuthProvider = ({ children, ...rest }: AuthProviderProps) => {
value={{
...providerValue,
...rest,
+ apiClient,
isLoginDialogOpen,
setIsLoginDialogOpen,
}}
diff --git a/client/src/utils/apiFetch.ts b/client/src/utils/apiFetch.ts
new file mode 100644
index 0000000..24fa205
--- /dev/null
+++ b/client/src/utils/apiFetch.ts
@@ -0,0 +1,18 @@
+const host = "http://localhost:5000/";
+
+export const createApiClient =
+ (token?: string): typeof fetch =>
+ (input, init = {}): Promise => {
+ const { headers, ...rest } = init;
+
+ const customInt: RequestInit = {
+ headers: {
+ "Content-Type": "application/json",
+ "X-Access-Token": token ?? "",
+ ...headers,
+ },
+ ...rest,
+ };
+
+ return fetch(host + input, customInt);
+ };
diff --git a/client/yarn.lock b/client/yarn.lock
index a72a42e..9b0dc44 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -1169,6 +1169,39 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
+"@date-io/core@^2.14.0":
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/@date-io/core/-/core-2.14.0.tgz#03e9b9b9fc8e4d561c32dd324df0f3ccd967ef14"
+ integrity sha512-qFN64hiFjmlDHJhu+9xMkdfDG2jLsggNxKXglnekUpXSq8faiqZgtHm2lsHCUuaPDTV6wuXHcCl8J1GQ5wLmPw==
+
+"@date-io/date-fns@^2.11.0":
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/@date-io/date-fns/-/date-fns-2.14.0.tgz#92ab150f488f294c135c873350d154803cebdbea"
+ integrity sha512-4fJctdVyOd5cKIKGaWUM+s3MUXMuzkZaHuTY15PH70kU1YTMrCoauA7hgQVx9qj0ZEbGrH9VSPYJYnYro7nKiA==
+ dependencies:
+ "@date-io/core" "^2.14.0"
+
+"@date-io/dayjs@^2.11.0":
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/@date-io/dayjs/-/dayjs-2.14.0.tgz#8d4e93e1d473bb5f25210866204dc33384ca4c20"
+ integrity sha512-4fRvNWaOh7AjvOyJ4h6FYMS7VHLQnIEeAV5ahv6sKYWx+1g1UwYup8h7+gPuoF+sW2hTScxi7PVaba2Jk/U8Og==
+ dependencies:
+ "@date-io/core" "^2.14.0"
+
+"@date-io/luxon@^2.11.1":
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/@date-io/luxon/-/luxon-2.14.0.tgz#cd1641229e00a899625895de3a31e3aaaf66629f"
+ integrity sha512-KmpBKkQFJ/YwZgVd0T3h+br/O0uL9ZdE7mn903VPAG2ZZncEmaUfUdYKFT7v7GyIKJ4KzCp379CRthEbxevEVg==
+ dependencies:
+ "@date-io/core" "^2.14.0"
+
+"@date-io/moment@^2.11.0":
+ version "2.14.0"
+ resolved "https://registry.yarnpkg.com/@date-io/moment/-/moment-2.14.0.tgz#8300abd6ae8c55d8edee90d118db3cef0b1d4f58"
+ integrity sha512-VsoLXs94GsZ49ecWuvFbsa081zEv2xxG7d+izJsqGa2L8RPZLlwk27ANh87+SNnOUpp+qy2AoCAf0mx4XXhioA==
+ dependencies:
+ "@date-io/core" "^2.14.0"
+
"@emotion/babel-plugin@^11.7.1":
version "11.9.2"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95"
@@ -1604,6 +1637,22 @@
dependencies:
"@babel/runtime" "^7.17.2"
+"@mui/lab@^5.0.0-alpha.89":
+ version "5.0.0-alpha.89"
+ resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.89.tgz#cc61560f16eebc3f44890835ee22cdc560ce4d48"
+ integrity sha512-u5bMi/V+Utwouo9awVzGasj/LudlRqPFyMo2L5/y60uFo0EaG17bt1jh/U7smQCdjd+7tvJ39HNMkEmIoGr7BQ==
+ dependencies:
+ "@babel/runtime" "^7.17.2"
+ "@mui/base" "5.0.0-alpha.88"
+ "@mui/system" "^5.8.7"
+ "@mui/utils" "^5.8.6"
+ "@mui/x-date-pickers" "5.0.0-alpha.1"
+ clsx "^1.2.0"
+ prop-types "^15.8.1"
+ react-is "^17.0.2"
+ react-transition-group "^4.4.2"
+ rifm "^0.12.1"
+
"@mui/material@^5.8.7":
version "5.8.7"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.8.7.tgz#28617c5b8a9e354e300f19fc38e1286ba1e15ad3"
@@ -1659,7 +1708,7 @@
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.4.tgz#4185c05d6df63ec673cda15feab80440abadc764"
integrity sha512-uveM3byMbthO+6tXZ1n2zm0W3uJCQYtwt/v5zV5I77v2v18u0ITkb8xwhsDD2i3V2Kye7SaNR6FFJ6lMuY/WqQ==
-"@mui/utils@^5.8.6":
+"@mui/utils@^5.6.0", "@mui/utils@^5.8.6":
version "5.8.6"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.8.6.tgz#543de64a64bb9135316ecfd91d75a8740544d79f"
integrity sha512-QM2Sd1xZo2jOt2Vz5Rmro+pi2FLJyiv4+OjxkUwXR3oUM65KSMAMLl/KNYU55s3W3DLRFP5MVwE4FhAbHseHAg==
@@ -1670,6 +1719,21 @@
prop-types "^15.8.1"
react-is "^17.0.2"
+"@mui/x-date-pickers@5.0.0-alpha.1":
+ version "5.0.0-alpha.1"
+ resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-5.0.0-alpha.1.tgz#7450b5544b9ed655db41891c74e2c5f652fbedb7"
+ integrity sha512-dLPkRiIn2Gr0momblxiOnIwrxn4SijVix+8e08mwAGWhiWcmWep1O9XTRDpZsjB0kjHYCf+kZjlRX4dxnj2acg==
+ dependencies:
+ "@date-io/date-fns" "^2.11.0"
+ "@date-io/dayjs" "^2.11.0"
+ "@date-io/luxon" "^2.11.1"
+ "@date-io/moment" "^2.11.0"
+ "@mui/utils" "^5.6.0"
+ clsx "^1.1.1"
+ prop-types "^15.7.2"
+ react-transition-group "^4.4.2"
+ rifm "^0.12.1"
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -3225,7 +3289,7 @@ cliui@^7.0.2:
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
-clsx@^1.2.0:
+clsx@^1.1.1, clsx@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
@@ -7346,7 +7410,7 @@ prompts@^2.0.1, prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.6.2, prop-types@^15.8.1:
+prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7479,6 +7543,11 @@ react-error-overlay@^6.0.11:
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb"
integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==
+react-hook-form@^7.33.1:
+ version "7.33.1"
+ resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.33.1.tgz#8c4410e3420788d3b804d62cc4c142915c2e46d0"
+ integrity sha512-ydTfTxEJdvgjCZBj5DDXRc58oTEfnFupEwwTAQ9FSKzykEJkX+3CiAkGtAMiZG7IPWHuzgT6AOBfogiKhUvKgg==
+
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -7781,6 +7850,11 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+rifm@^0.12.1:
+ version "0.12.1"
+ resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.12.1.tgz#8fa77f45b7f1cda2a0068787ac821f0593967ac4"
+ integrity sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==
+
rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"