diff --git a/frontend/app/src/App.tsx b/frontend/app/src/App.tsx
index 24f06dc..d51d5dd 100644
--- a/frontend/app/src/App.tsx
+++ b/frontend/app/src/App.tsx
@@ -1,18 +1,27 @@
-import React from "react";
-import { ConfigProvider } from "antd";
+import React, { createContext } from "react";
+import { ConfigProvider, message } from "antd";
import "./App.css";
import HeaderComponent from "./components/HeaderComponent";
import { theme } from "./config/style";
import { Provider } from "react-redux";
import { store } from "./config/store";
+import { MessageInstance } from "antd/es/message/interface";
+
+export const MessageContext = createContext({} as MessageInstance);
const App = () => {
+ const [messageApi, contextHolder] = message.useMessage({
+ duration: 2,
+ });
return (
-
-
-
+
+
+ {contextHolder}
+
+
+
);
diff --git a/frontend/app/src/components/AuthModal.tsx b/frontend/app/src/components/AuthModal.tsx
index 063fbf5..dae0153 100644
--- a/frontend/app/src/components/AuthModal.tsx
+++ b/frontend/app/src/components/AuthModal.tsx
@@ -1,7 +1,9 @@
-import { Form, Input, Menu, MenuProps, Modal } from "antd";
+import { Form, Input, Menu, MenuProps, Modal, Spin } from "antd";
import "./styles.css";
import { KeyOutlined, UserAddOutlined } from "@ant-design/icons";
-import React, { useState } from "react";
+import React, { useContext, useState } from "react";
+import { useLoginMutation, useRegisterMutation } from "../slice/AuthApi";
+import { MessageContext } from "../App";
const AuthModal = (props: {
open: boolean;
@@ -11,12 +13,20 @@ const AuthModal = (props: {
const [registerForm] = Form.useForm();
const [current, setCurrent] = useState("login");
+ const messageApi = useContext(MessageContext);
- const submitLoginForm = (formData: {
- username: string;
- password: string;
- }) => {
- console.log(formData);
+ const [loginUser, { isLoading: isLoggingIn }] = useLoginMutation();
+ const [registerUser, { isLoading: isRegistering }] = useRegisterMutation();
+
+ const submitLoginForm = (data: { username: string; password: string }) => {
+ const formData = new FormData();
+ formData.append("username", data.username);
+ formData.append("password", data.password);
+
+ loginUser(formData)
+ .unwrap()
+ .then(() => props.setOpen(false))
+ .catch(() => messageApi.error("Login failed!"));
};
const submitRegisterForm = (formData: {
@@ -26,6 +36,10 @@ const AuthModal = (props: {
password2: string;
}) => {
console.log(formData);
+ registerUser(formData)
+ .unwrap()
+ .then(() => props.setOpen(false))
+ .catch(() => messageApi.error("Registration failed!"));
};
const items: MenuProps["items"] = [
@@ -42,113 +56,118 @@ const AuthModal = (props: {
];
return (
- props.setOpen(false)}
- onOk={() => {
- current === "register" && registerForm.submit();
- current === "login" && loginForm.submit();
- }}
- okText={current === "login" ? "Log In" : "Register"}
- >
-
+ props.setOpen(false)}
+ onOk={() => {
+ current === "register" && registerForm.submit();
+ current === "login" && loginForm.submit();
+ }}
+ okText={current === "login" ? "Log In" : "Register"}
+ confirmLoading={isLoggingIn}
+ >
+
-
-
-
-
-
-
- ({
- validator(_, value) {
- if (!value || getFieldValue("password") === value) {
- return Promise.resolve();
- }
- return Promise.reject(
- new Error("The new password that you entered do not match!")
- );
+
+
+
+
+
+
+
+
+
+ ({
+ validator(_, value) {
+ if (!value || getFieldValue("password") === value) {
+ return Promise.resolve();
+ }
+ return Promise.reject(
+ new Error(
+ "The new password that you entered do not match!"
+ )
+ );
+ },
+ }),
+ ]}
+ >
+
+
+
+ ) : (
+
-
- ) : (
-
-
-
-
-
-
-
- )}
-
+
+
+
+
+
+
+
+ )}
+
+
);
};
diff --git a/frontend/app/src/config/store.ts b/frontend/app/src/config/store.ts
index 44a211b..64f4f46 100644
--- a/frontend/app/src/config/store.ts
+++ b/frontend/app/src/config/store.ts
@@ -1,18 +1,37 @@
-import { configureStore } from '@reduxjs/toolkit'
-import { setupListeners } from '@reduxjs/toolkit/query'
-import { AuthApi } from '../slice/AuthApi'
+import { configureStore, createAction, createReducer } from "@reduxjs/toolkit";
+import { setupListeners } from "@reduxjs/toolkit/query";
+import { AuthApi } from "../slice/AuthApi";
+
+export type authState = {
+ token: string | null;
+};
+
+const initialAuthState: authState = {
+ token: null,
+};
export const store = configureStore({
reducer: {
// Add the generated reducer as a specific top-level slice
[AuthApi.reducerPath]: AuthApi.reducer,
+ auth: createReducer(initialAuthState, (builder) => {
+ builder.addCase(createAction("auth/token"), (state, _) => {
+ const token: string | null = localStorage.getItem("token");
+ if (token) {
+ state.token = token;
+ }
+ });
+ }),
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(AuthApi.middleware),
-})
+});
// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
-setupListeners(store.dispatch)
\ No newline at end of file
+setupListeners(store.dispatch);
+
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
diff --git a/frontend/app/src/config/style.ts b/frontend/app/src/config/style.ts
index 822941e..cb47658 100644
--- a/frontend/app/src/config/style.ts
+++ b/frontend/app/src/config/style.ts
@@ -1,23 +1,26 @@
export const theme = {
- components: {
- Modal: {
- contentBg: "#001529"
- },
- Form: {
- labelColor: "#77828c"
- },
- Input: {
- activeBg: "#001c36",
- },
- Button: {
- primaryColor: "#001529"
- }
+ components: {
+ Modal: {
+ contentBg: "#001529",
},
- token: {
- colorText: "#ffffff",
- colorBgContainer: "#001c36",
- colorIcon: "#77828c",
- colorPrimary: "#ffffff",
- colorPrimaryHover: "#001529",
- }
-};
\ No newline at end of file
+ Form: {
+ labelColor: "#77828c",
+ },
+ Input: {
+ activeBg: "#001c36",
+ },
+ Button: {
+ primaryColor: "#001529",
+ },
+ Message: {
+ contentBg: "#001c36",
+ },
+ },
+ token: {
+ colorText: "#ffffff",
+ colorBgContainer: "#001c36",
+ colorIcon: "#77828c",
+ colorPrimary: "#ffffff",
+ colorPrimaryHover: "#001529",
+ },
+};
diff --git a/frontend/app/src/slice/AuthApi.ts b/frontend/app/src/slice/AuthApi.ts
index cb9d6d7..9c76edc 100644
--- a/frontend/app/src/slice/AuthApi.ts
+++ b/frontend/app/src/slice/AuthApi.ts
@@ -1,14 +1,58 @@
-import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
-import { baseUrl } from '../config/baseUrl'
+import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
+import { baseUrl } from "../config/baseUrl";
+import { RootState } from "../config/store";
+
+export interface User {
+ username: string;
+ name: string;
+}
+
+export interface UserResponse {
+ user: User;
+ token: string;
+}
+
+export interface RegisterRequest {
+ username: string;
+ name: string | undefined;
+ password: string;
+ password2: string;
+}
export const AuthApi = createApi({
- reducerPath: 'AuthApi',
- baseQuery: fetchBaseQuery({ baseUrl: `${baseUrl}auth/` }),
+ reducerPath: "AuthApi",
+ baseQuery: fetchBaseQuery({
+ baseUrl: `${baseUrl}auth/`,
+ prepareHeaders: (headers, { getState }) => {
+ // By default, if we have a token in the store, let's use that for authenticated requests
+ const token = (getState() as RootState).auth.token;
+ if (token) {
+ headers.set("authorization", `Bearer ${token}`);
+ }
+ return headers;
+ },
+ }),
endpoints: (builder) => ({
getUser: builder.query({
- query: () => 'me/',
+ query: () => "me",
+ }),
+ login: builder.mutation({
+ query: (data: FormData) => ({
+ url: "token",
+ method: "POST",
+ body: data,
+ formData: true,
+ }),
+ }),
+ register: builder.mutation({
+ query: (data: RegisterRequest) => ({
+ url: "register",
+ method: "POST",
+ body: data,
+ }),
}),
}),
-})
+});
-export const { useGetUserQuery } = AuthApi
\ No newline at end of file
+export const { useGetUserQuery, useLoginMutation, useRegisterMutation } =
+ AuthApi;