Added auth to endpoints

This commit is contained in:
Benjamin Sherriff
2023-10-18 15:36:36 -04:00
parent 7ba0e070ac
commit f072a47d22
12 changed files with 160 additions and 84 deletions

View File

@@ -79,9 +79,30 @@ impl InsertUser {
} }
} }
#[derive(Debug, Serialize, Deserialize)]
pub struct ResponseUser {
pub email: String,
pub role: String,
pub first_name: String,
pub last_name: String,
}
impl From<QueryUser> for ResponseUser {
fn from(user: QueryUser) -> Self {
ResponseUser {
email: user.email,
role: user.role,
first_name: user.first_name,
last_name: user.last_name,
}
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct JwtAuth { pub struct JwtAuth {
pub access_token_uuid: uuid::Uuid pub access_token_uuid: uuid::Uuid,
pub email: String,
pub role: String,
} }
impl FromRequest for JwtAuth { impl FromRequest for JwtAuth {
@@ -140,8 +161,8 @@ impl FromRequest for JwtAuth {
}; };
match QueryUser::get_by_email(&user_email) { match QueryUser::get_by_email(&user_email) {
Ok(_) => { Ok(user) => {
ready(Ok(JwtAuth { access_token_uuid })) ready(Ok(JwtAuth { access_token_uuid, email: user.email, role: user.role }))
} }
Err(err) => return ready(Err(ActixError::from(ServiceError { Err(err) => return ready(Err(ActixError::from(ServiceError {
status: 500, status: 500,

View File

@@ -5,7 +5,7 @@ use log::error;
use redis::AsyncCommands; use redis::AsyncCommands;
use siren::ServiceError; use siren::ServiceError;
use crate::{auth::{LoginRequest, RegisterUser, InsertUser, QueryUser, verify_password, generate_token, JwtAuth}, db}; use crate::{auth::{LoginRequest, RegisterUser, InsertUser, QueryUser, verify_password, generate_token, JwtAuth, ResponseUser}, db};
#[post("/register")] #[post("/register")]
async fn register(user: web::Json<RegisterUser>) -> HttpResponse { async fn register(user: web::Json<RegisterUser>) -> HttpResponse {
@@ -96,17 +96,17 @@ async fn login(request: web::Json<LoginRequest>) -> HttpResponse {
let access_cookie = Cookie::build("access_token", access_token_details.token.clone().unwrap()) let access_cookie = Cookie::build("access_token", access_token_details.token.clone().unwrap())
.path("/") .path("/")
.max_age(Duration::new(access_token_max_age, 0)) .max_age(Duration::new(access_token_max_age * 60, 0))
.http_only(true) .http_only(true)
.finish(); .finish();
let refresh_cookie = Cookie::build("refresh_token", refresh_token_details.token.clone().unwrap()) let refresh_cookie = Cookie::build("refresh_token", refresh_token_details.token.clone().unwrap())
.path("/") .path("/")
.max_age(Duration::new(refresh_token_max_age, 0)) .max_age(Duration::new(refresh_token_max_age * 60, 0))
.http_only(true) .http_only(true)
.finish(); .finish();
let logged_in_cookie = Cookie::build("logged_in", "true") let logged_in_cookie = Cookie::build("logged_in", "true")
.path("/") .path("/")
.max_age(Duration::new(access_token_max_age, 0)) .max_age(Duration::new(access_token_max_age * 60, 0))
.http_only(false) .http_only(false)
.finish(); .finish();
@@ -135,10 +135,24 @@ async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
#[get("/me")] #[get("/me")]
async fn me(auth: JwtAuth) -> HttpResponse { async fn me(auth: JwtAuth) -> HttpResponse {
HttpResponse::Ok().json(auth) let query_user = match QueryUser::get_by_email(&auth.email) {
Ok(user) => user,
Err(err) => return ResponseError::error_response(&err)
};
let user: ResponseUser = query_user.into();
HttpResponse::Ok().json(user)
} }
pub fn init_routes(config: &mut web::ServiceConfig) { pub fn init_routes(config: &mut web::ServiceConfig) {
let r = RegisterUser {
email: "admin".to_string(),
password: "admin".to_string(),
first_name: "Admin".to_string(),
last_name: "Admin".to_string(),
};
let mut u = r.convert_to_insert().unwrap();
u.role = "admin".to_string();
let _ = InsertUser::insert(u);
config.service(web::scope("auth") config.service(web::scope("auth")
.service(register) .service(register)
.service(login) .service(login)

View File

@@ -6,10 +6,10 @@ use serde::{Serialize, Deserialize};
use serenity::model::prelude::{GuildChannel, ChannelType}; use serenity::model::prelude::{GuildChannel, ChannelType};
use siren::ServiceError; use siren::ServiceError;
use crate::{AppState, bot::commands::audio::{play::play_track, join}, db::guilds::{InsertGuild, QueryGuild}}; use crate::{AppState, bot::commands::audio::{play::play_track, join}, db::guilds::{InsertGuild, QueryGuild}, auth::JwtAuth};
#[get("/guilds")] #[get("/guilds")]
async fn get_guilds(data: web::Data<Arc<AppState>>) -> HttpResponse { async fn get_guilds(data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let guild_results = &data.http.get_guilds(None, None).await; let guild_results = &data.http.get_guilds(None, None).await;
let guilds = match guild_results { let guilds = match guild_results {
Ok(guilds) => guilds, Ok(guilds) => guilds,
@@ -22,7 +22,7 @@ async fn get_guilds(data: web::Data<Arc<AppState>>) -> HttpResponse {
} }
#[get("/{id}/text")] #[get("/{id}/text")]
async fn get_text_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn get_text_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let channel_results = &data.http.get_channels(id.parse::<u64>().unwrap()).await; let channel_results = &data.http.get_channels(id.parse::<u64>().unwrap()).await;
let channels = match channel_results { let channels = match channel_results {
Ok(channels) => channels.iter().filter(|c| c.kind == ChannelType::Text).collect::<Vec<&GuildChannel>>(), Ok(channels) => channels.iter().filter(|c| c.kind == ChannelType::Text).collect::<Vec<&GuildChannel>>(),
@@ -35,7 +35,7 @@ async fn get_text_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>
} }
#[get("/{id}/voice")] #[get("/{id}/voice")]
async fn get_voice_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn get_voice_channels(id: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let channel_results = &data.http.get_channels(id.parse::<u64>().unwrap()).await; let channel_results = &data.http.get_channels(id.parse::<u64>().unwrap()).await;
let channels = match channel_results { let channels = match channel_results {
Ok(channels) => channels.iter().filter(|c| c.kind == ChannelType::Voice).collect::<Vec<&GuildChannel>>(), Ok(channels) => channels.iter().filter(|c| c.kind == ChannelType::Voice).collect::<Vec<&GuildChannel>>(),
@@ -53,7 +53,7 @@ struct ChannelMessage {
} }
#[post("/{guild_id}/text/{channel_id}/message")] #[post("/{guild_id}/text/{channel_id}/message")]
async fn send_message(path: web::Path<(String, String)>, text: web::Json<ChannelMessage>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn send_message(path: web::Path<(String, String)>, text: web::Json<ChannelMessage>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let (guild_id, channel_id) = path.into_inner(); let (guild_id, channel_id) = path.into_inner();
let guild_id = match guild_id.parse::<u64>() { let guild_id = match guild_id.parse::<u64>() {
Ok(id) => id, Ok(id) => id,
@@ -115,7 +115,7 @@ struct PlayRequest {
} }
#[post("/{guild_id}/voice/{channel_id}/play")] #[post("/{guild_id}/voice/{channel_id}/play")]
async fn play(path: web::Path<(String, String)>, play_request: web::Json<PlayRequest>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn play(path: web::Path<(String, String)>, play_request: web::Json<PlayRequest>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let (guild_id, channel_id) = path.into_inner(); let (guild_id, channel_id) = path.into_inner();
let guild_id = match guild_id.parse::<u64>() { let guild_id = match guild_id.parse::<u64>() {
Ok(id) => id, Ok(id) => id,
@@ -167,7 +167,7 @@ async fn play(path: web::Path<(String, String)>, play_request: web::Json<PlayReq
} }
#[post("/{guild_id}/voice/stop")] #[post("/{guild_id}/voice/stop")]
async fn stop(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn stop(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let guild_id = path.into_inner(); let guild_id = path.into_inner();
let guild_id = match guild_id.parse::<u64>() { let guild_id = match guild_id.parse::<u64>() {
Ok(id) => id, Ok(id) => id,
@@ -189,7 +189,7 @@ async fn stop(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpRe
} }
#[post("/{guild_id}/voice/resume")] #[post("/{guild_id}/voice/resume")]
async fn resume(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn resume(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let guild_id = path.into_inner(); let guild_id = path.into_inner();
let guild_id = match guild_id.parse::<u64>() { let guild_id = match guild_id.parse::<u64>() {
Ok(id) => id, Ok(id) => id,
@@ -217,7 +217,7 @@ async fn resume(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> Http
} }
#[post("/{guild_id}/voice/pause")] #[post("/{guild_id}/voice/pause")]
async fn pause(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn pause(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let guild_id = path.into_inner(); let guild_id = path.into_inner();
let guild_id = match guild_id.parse::<u64>() { let guild_id = match guild_id.parse::<u64>() {
Ok(id) => id, Ok(id) => id,
@@ -250,7 +250,7 @@ struct SetVolume {
} }
#[get("/{guild_id}/voice/volume")] #[get("/{guild_id}/voice/volume")]
async fn get_volume(path: web::Path<String>) -> HttpResponse { async fn get_volume(path: web::Path<String>, auth: JwtAuth) -> HttpResponse {
let guild_id = path.into_inner(); let guild_id = path.into_inner();
let guild_id = match guild_id.parse::<u64>() { let guild_id = match guild_id.parse::<u64>() {
Ok(id) => id, Ok(id) => id,
@@ -278,7 +278,7 @@ async fn get_volume(path: web::Path<String>) -> HttpResponse {
} }
#[post("/{guild_id}/voice/volume")] #[post("/{guild_id}/voice/volume")]
async fn set_volume(path: web::Path<String>, volume: web::Json::<SetVolume>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn set_volume(path: web::Path<String>, volume: web::Json::<SetVolume>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let guild_id = path.into_inner(); let guild_id = path.into_inner();
let guild_id = match guild_id.parse::<u64>() { let guild_id = match guild_id.parse::<u64>() {
Ok(id) => id, Ok(id) => id,
@@ -307,7 +307,7 @@ async fn set_volume(path: web::Path<String>, volume: web::Json::<SetVolume>, dat
} }
#[post("/{guild_id}/voice/skip")] #[post("/{guild_id}/voice/skip")]
async fn skip(path: web::Path<String>, data: web::Data<Arc<AppState>>) -> HttpResponse { async fn skip(path: web::Path<String>, data: web::Data<Arc<AppState>>, auth: JwtAuth) -> HttpResponse {
let guild_id = path.into_inner(); let guild_id = path.into_inner();
let guild_id = match guild_id.parse::<u64>() { let guild_id = match guild_id.parse::<u64>() {
Ok(id) => id, Ok(id) => id,

View File

@@ -3,7 +3,7 @@ use log::error;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use siren::{GetResponse, Metadata, ServiceError}; use siren::{GetResponse, Metadata, ServiceError};
use crate::db::messages::{QueryMessage, QueryFilters, InsertMessage}; use crate::{db::messages::{QueryMessage, QueryFilters, InsertMessage}, auth::JwtAuth};
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct GetAllParams { struct GetAllParams {
@@ -21,7 +21,7 @@ struct GetAllParams {
} }
#[get("/messages")] #[get("/messages")]
async fn get_all(req: HttpRequest) -> HttpResponse { async fn get_all(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
let params = match web::Query::<GetAllParams>::from_query(req.query_string()) { let params = match web::Query::<GetAllParams>::from_query(req.query_string()) {
Ok(params) => params, Ok(params) => params,
Err(err) => return ResponseError::error_response(&ServiceError { Err(err) => return ResponseError::error_response(&ServiceError {
@@ -64,7 +64,7 @@ async fn get_all(req: HttpRequest) -> HttpResponse {
} }
#[post("/messages")] #[post("/messages")]
async fn create(message: web::Json<InsertMessage>) -> HttpResponse { async fn create(message: web::Json<InsertMessage>, auth: JwtAuth) -> HttpResponse {
match InsertMessage::insert(message.into_inner()) { match InsertMessage::insert(message.into_inner()) {
Ok(message) => HttpResponse::Created().json(message), Ok(message) => HttpResponse::Created().json(message),
Err(err) => { Err(err) => {

View File

@@ -3,7 +3,7 @@ use log::error;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use siren::{GetResponse, Metadata, ServiceError}; use siren::{GetResponse, Metadata, ServiceError};
use crate::db::spells::{QuerySpell, QueryFilters}; use crate::{db::spells::{QuerySpell, QueryFilters}, auth::JwtAuth};
use super::{Spell, InsertSpell}; use super::{Spell, InsertSpell};
@@ -134,7 +134,7 @@ async fn get_by_id(id: web::Path<String>) -> HttpResponse {
} }
#[post("/spells")] #[post("/spells")]
async fn create(spell: web::Json<Spell>) -> HttpResponse { async fn create(spell: web::Json<Spell>, auth: JwtAuth) -> HttpResponse {
match InsertSpell::insert(spell.into_inner().into()) { match InsertSpell::insert(spell.into_inner().into()) {
Ok(spell) => HttpResponse::Created().json(Spell::from(spell)), Ok(spell) => HttpResponse::Created().json(Spell::from(spell)),
Err(err) => { Err(err) => {
@@ -145,7 +145,7 @@ async fn create(spell: web::Json<Spell>) -> HttpResponse {
} }
#[put("/spells/{id}")] #[put("/spells/{id}")]
async fn update(id: web::Path<String>, spell: web::Json<Spell>) -> HttpResponse { async fn update(id: web::Path<String>, spell: web::Json<Spell>, auth: JwtAuth) -> HttpResponse {
let id = match id.parse::<i32>() { let id = match id.parse::<i32>() {
Ok(id) => id, Ok(id) => id,
Err(err) => return ResponseError::error_response(&ServiceError { Err(err) => return ResponseError::error_response(&ServiceError {
@@ -163,7 +163,7 @@ async fn update(id: web::Path<String>, spell: web::Json<Spell>) -> HttpResponse
} }
#[delete("/spells/{id}")] #[delete("/spells/{id}")]
async fn delete(id: web::Path<String>) -> HttpResponse { async fn delete(id: web::Path<String>, auth: JwtAuth) -> HttpResponse {
let id = match id.parse::<i32>() { let id = match id.parse::<i32>() {
Ok(id) => id, Ok(id) => id,
Err(err) => return ResponseError::error_response(&ServiceError { Err(err) => return ResponseError::error_response(&ServiceError {

13
ui/src/api/auth.ts Normal file
View File

@@ -0,0 +1,13 @@
import { getRequest, postRequest } from '.';
export async function login(email: string, password: string) {
return await postRequest('auth/login', { email, password }, { withCredentials: true });
}
export async function logout() {
return await postRequest('auth/logout', {}, { withCredentials: true });
}
export async function me() {
return await getRequest('auth/me', { withCredentials: true });
}

6
ui/src/api/auth.types.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface User {
email: string;
role: string;
first_name: string;
last_name: string;
}

View File

@@ -2,12 +2,12 @@ import { getRequest, postRequest } from '.';
import { GuildChannel, GuildInfo } from './guilds.types'; import { GuildChannel, GuildInfo } from './guilds.types';
export async function getGuilds(): Promise<GuildInfo[]> { export async function getGuilds(): Promise<GuildInfo[]> {
const response = await getRequest('guilds', {}); const response = await getRequest('guilds');
return response?.data || { data: [] }; return response?.data || { data: [] };
} }
export async function getTextChannels(guildId: number): Promise<GuildChannel[]> { export async function getTextChannels(guildId: number): Promise<GuildChannel[]> {
const response = await getRequest(`guilds/${guildId}/text`, {}); const response = await getRequest(`guilds/${guildId}/text`);
return response?.data || { data: [] }; return response?.data || { data: [] };
} }
@@ -16,7 +16,7 @@ export async function sendMessage(guildId: number, channelId: number, message: s
} }
export async function getVoiceChannels(guildId: number): Promise<GuildChannel[]> { export async function getVoiceChannels(guildId: number): Promise<GuildChannel[]> {
const response = await getRequest(`guilds/${guildId}/voice`, {}); const response = await getRequest(`guilds/${guildId}/voice`);
return response?.data || { data: [] }; return response?.data || { data: [] };
} }
@@ -45,6 +45,6 @@ export async function skipTrack(guildId: number): Promise<void> {
} }
export async function getVolume(guildId: number): Promise<number> { export async function getVolume(guildId: number): Promise<number> {
const response = await getRequest(`guilds/${guildId}/voice/volume`, {}); const response = await getRequest(`guilds/${guildId}/voice/volume`);
return response?.data?.volume || 0; return response?.data?.volume || 0;
} }

View File

@@ -5,10 +5,10 @@ const servicePort = process.env.SERVICE_PORT || 5000;
export async function getRequest( export async function getRequest(
url: string, url: string,
params: AxiosRequestConfig<any> config?: AxiosRequestConfig<any>
): Promise<AxiosResponse<any, any> | undefined> { ): Promise<AxiosResponse<any, any> | undefined> {
const response = await axios const response = await axios
.get(`${serviceHost}:${servicePort}/${url}`, { params }) .get(`${serviceHost}:${servicePort}/${url}`, config)
.catch((error) => console.error(error)); .catch((error) => console.error(error));
return response || undefined; return response || undefined;
} }

View File

@@ -20,6 +20,7 @@ interface GetSpellsParams {
export async function getSpells(params?: GetSpellsParams): Promise<GetSpellsResponse> { export async function getSpells(params?: GetSpellsParams): Promise<GetSpellsResponse> {
const response = await getRequest('dnd/spells', { const response = await getRequest('dnd/spells', {
params: {
name: params?.name, name: params?.name,
like_name: params?.like_name, like_name: params?.like_name,
schools: params?.schools?.join(','), schools: params?.schools?.join(','),
@@ -34,6 +35,7 @@ export async function getSpells(params?: GetSpellsParams): Promise<GetSpellsResp
attack_type: params?.attack_type?.join(','), attack_type: params?.attack_type?.join(','),
limit: params?.limit, limit: params?.limit,
page: params?.page page: params?.page
}
}); });
return response?.data || { data: [] }; return response?.data || { data: [] };
} }

View File

@@ -1,13 +0,0 @@
import { getRequest, postRequest } from '.';
export async function login(email: string, password: string) {
return await postRequest('users/login', { email, password }, { withCredentials: true });
}
export async function logout() {
return await postRequest('users/logout', {}, { withCredentials: true });
}
export async function ping() {
return await getRequest('users/ping', { withCredentials: true });
}

View File

@@ -21,9 +21,16 @@ import {
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { login, logout, ping } from '@/api/users'; import { login, logout, me } from '@/api/auth';
import { User } from '@/api/auth.types';
const headerItems = [ interface HeaderItem {
name: string;
link: string;
role?: string;
}
const headerItems: HeaderItem[] = [
{ {
name: 'Races', name: 'Races',
link: '/races' link: '/races'
@@ -54,24 +61,38 @@ const headerItems = [
}, },
{ {
name: 'Management', name: 'Management',
link: '/management' link: '/management',
role: 'admin'
} }
]; ];
export default function Topbar() { export default function Topbar() {
const pathName = usePathname(); const pathName = usePathname();
const [showLogin, setShowLogin] = useState(false); const [showLogin, setShowLogin] = useState(false);
const [authenticated, setAuthenticated] = useState(false); const [headers, setHeaders] = useState<HeaderItem[]>([]);
// Check if the auth cookie is set const [user, setUser] = useState<User | undefined>(undefined);
// If it is, show the user avatar
// If not, show the login button
useEffect(() => { useEffect(() => {
console.log('cookies', Cookies.get()); if (Cookies.get('logged_in')) {
if (Cookies.get('auth')) { me().then((response) => {
setAuthenticated(true); if (response?.status == 200) {
setUser(response.data);
}
});
} else {
setUser(undefined);
} }
}, []); }, []);
useEffect(() => {
const h: HeaderItem[] = [];
headerItems.forEach((item) => {
if (item.role == undefined || user?.role == item.role) {
h.push(item);
}
setHeaders(h);
});
}, [user]);
return ( return (
<> <>
<nav className='navbar'> <nav className='navbar'>
@@ -80,7 +101,7 @@ export default function Topbar() {
Siren Siren
</Link> </Link>
<div className='header-items'> <div className='header-items'>
{headerItems.map((item) => ( {headers.map((item) => (
<Link className={`header-item ${pathName == item.link && 'active'}`} href={item.link} key={item.name}> <Link className={`header-item ${pathName == item.link && 'active'}`} href={item.link} key={item.name}>
{item.name} {item.name}
</Link> </Link>
@@ -93,26 +114,25 @@ export default function Topbar() {
<Avatar style={{ cursor: 'pointer' }} /> <Avatar style={{ cursor: 'pointer' }} />
</Menu.Target> </Menu.Target>
<Menu.Dropdown> <Menu.Dropdown>
{!authenticated && <Menu.Item onClick={() => setShowLogin(true)}>Login</Menu.Item>} {!user && <Menu.Item onClick={() => setShowLogin(true)}>Login</Menu.Item>}
{authenticated && ( {user && (
<Menu.Item <Menu.Item
onClick={async () => { onClick={async () => {
const response = await logout(); const response = await logout();
if (response?.status == 200) { if (response?.status == 200) {
Cookies.remove('auth'); Cookies.remove('logged_in');
setAuthenticated(false); setUser(undefined);
} }
}} }}
> >
Logout Logout
</Menu.Item> </Menu.Item>
)} )}
<Menu.Item onClick={() => ping()}>Ping</Menu.Item>
</Menu.Dropdown> </Menu.Dropdown>
</Menu> </Menu>
</div> </div>
</nav> </nav>
<LoginModal showLogin={showLogin} setShowLogin={setShowLogin} setAuthenticated={setAuthenticated} /> <LoginModal showLogin={showLogin} setShowLogin={setShowLogin} setUser={setUser} />
</> </>
); );
} }
@@ -120,20 +140,29 @@ export default function Topbar() {
function LoginModal({ function LoginModal({
showLogin, showLogin,
setShowLogin, setShowLogin,
setAuthenticated setUser
}: { }: {
showLogin: boolean; showLogin: boolean;
setShowLogin: (show: boolean) => void; setShowLogin: (show: boolean) => void;
setAuthenticated: (authenticated: boolean) => void; setUser: (user: User) => void;
}) { }) {
const form = useForm({ const form = useForm({
initialValues: { initialValues: {
email: '', email: '',
password: '' password: '',
remember: false
} }
}); });
function onClose() {
setShowLogin(false);
if (!form.values.remember) {
form.reset();
}
}
return ( return (
<Modal opened={showLogin} onClose={() => setShowLogin(false)} withCloseButton={false}> <Modal opened={showLogin} onClose={onClose} withCloseButton={false}>
<Container> <Container>
<Title ta='center'>Welcome back!</Title> <Title ta='center'>Welcome back!</Title>
<Text c='dimmed' size='sm' ta='center' mt={5}> <Text c='dimmed' size='sm' ta='center' mt={5}>
@@ -148,8 +177,12 @@ function LoginModal({
onSubmit={form.onSubmit(async (values) => { onSubmit={form.onSubmit(async (values) => {
const response = await login(values.email, values.password); const response = await login(values.email, values.password);
if (response?.status == 200) { if (response?.status == 200) {
setShowLogin(false); me().then((response) => {
setAuthenticated(true); if (response?.status == 200) {
setUser(response.data);
}
onClose();
});
} }
})} })}
> >
@@ -162,7 +195,7 @@ function LoginModal({
{...form.getInputProps('password')} {...form.getInputProps('password')}
/> />
<Group justify='space-between' mt='lg'> <Group justify='space-between' mt='lg'>
<Checkbox label='Remember me' /> <Checkbox label='Remember me' {...form.getInputProps('remember')} />
<Anchor component='button' size='sm'> <Anchor component='button' size='sm'>
Forgot password? Forgot password?
</Anchor> </Anchor>