Skip to content

Commit 830aba6

Browse files
committed
Improve login functionality and add logout ability
1 parent ab552a9 commit 830aba6

File tree

10 files changed

+136
-52
lines changed

10 files changed

+136
-52
lines changed

backend/src/controllers/auth.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from uuid import uuid4
2-
from flask import Blueprint, make_response, request, session
2+
from flask import Blueprint, make_response, redirect, request, session
3+
from src.flask_config import Config
34
from src.spotify import SpotifyClient
45

56

@@ -15,6 +16,15 @@ def login():
1516
query_string = spotify.get_login_query_string(state)
1617
return "https://accounts.spotify.com/authorize?" + query_string
1718

19+
@auth_controller.route("logout")
20+
def logout():
21+
resp = make_response("Logged out")
22+
resp.delete_cookie("spotify_access_token")
23+
resp.delete_cookie("spotify_refresh_token")
24+
resp.delete_cookie("user_id")
25+
resp.delete_cookie("session")
26+
return resp
27+
1828
@auth_controller.route("get-user-code")
1929
def auth_redirect():
2030
code = request.args.get("code")

frontend/src/Login.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import React, { FC } from "react";
2-
import Box from "./components/Box";
32
import { login } from "./api";
3+
import Button from "./components/Button";
44

55
export const Login: FC = () => {
66
return (
7-
<div>
8-
<Box>
9-
<button onClick={login}>Click to login</button>
10-
</Box>
7+
<div className="w-full flex">
8+
<Button className="w-1/3 mx-auto mt-12" onClick={login}>
9+
Click to login
10+
</Button>
1111
</div>
1212
);
1313
};

frontend/src/api/index.ts

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ export const login = async (): Promise<void> => {
2020
});
2121
};
2222

23+
export const logout = async (): Promise<void> => {
24+
return fetch(`${backendUrl}/auth/logout`, {
25+
credentials: "include",
26+
}).then(async response => {
27+
const redirectUrl = await response.text();
28+
console.log(redirectUrl)
29+
window.open("/", "_self");
30+
});
31+
}
32+
2333
export const getCurrentUserDetails = async (): Promise<User> => {
2434
return jsonRequest(
2535
`spotify/current-user`,

frontend/src/components/Box.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import React, { FC } from "react";
22

33
const Box: FC<
44
React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
5-
> = (props) => {
5+
> = ({className, ...props}) => {
66
return (
77
<div
8-
className={`p-2 rounded border-solid border border-primary-500 ${props.className}`}
8+
className={`p-2 rounded border-solid border border-primary-500 ${className}`}
99
{...props}
1010
>
1111
{props.children}

frontend/src/components/Button.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import React, { FC } from "react";
22

3-
const CustomButton: FC<
3+
const Button: FC<
44
React.DetailedHTMLProps<
55
React.ButtonHTMLAttributes<HTMLButtonElement>,
66
HTMLButtonElement
77
>
8-
> = (props) => {
8+
> = ({className, ...props}) => {
99
return (
1010
<button
1111
{...props}
12-
className={`bg-secondary-300 rounded p-2 cursor-pointer hover:bg-secondary-500 ${props.className}`}
12+
className={`bg-secondary-300 rounded p-2 cursor-pointer hover:bg-secondary-500 active:bg-secondary-600 ${className}`}
1313
></button>
1414
);
1515
};
1616

17-
export default CustomButton;
17+
export default Button;
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React, { FC, ReactNode, useEffect, useRef, useState } from "react";
2+
3+
interface ModalProps {
4+
children: ReactNode;
5+
trigger: ReactNode;
6+
isMenuOpen: boolean;
7+
closeMenu: () => void;
8+
}
9+
10+
const DropdownMenu: FC<ModalProps> = ({ children, trigger, isMenuOpen, closeMenu }) => {
11+
const dropdownRef = useRef<HTMLDivElement>(null);
12+
useEffect(() => {
13+
function handleClickOutside({target}: MouseEvent) {
14+
if (dropdownRef.current && !dropdownRef.current.contains(target as Node)) {
15+
closeMenu();
16+
}
17+
}
18+
document.addEventListener("mousedown", handleClickOutside);
19+
20+
return () => {
21+
document.removeEventListener("mousedown", handleClickOutside);
22+
};
23+
}, [dropdownRef, closeMenu]);
24+
25+
return (
26+
<div className="relative" ref={dropdownRef}>
27+
{trigger}
28+
{isMenuOpen &&
29+
<div
30+
31+
className={`bg-background-popover w-full h-fit absolute opacity-100 origin-top-right left-0 rounded-xl shadow p-6 z-50`}
32+
role={"menu"}
33+
>
34+
{children}
35+
</div>
36+
}
37+
</div>
38+
);
39+
};
40+
41+
export default DropdownMenu;

frontend/src/presentational/Header.tsx

-39
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from "react";
2+
import { Link } from "react-router-dom";
3+
import { getCurrentUserDetails, login } from "../../api";
4+
import { useQuery } from "@tanstack/react-query";
5+
import { User } from "../../interfaces/User";
6+
import UserMenu from "./UserMenu";
7+
8+
const Header = () => {
9+
const { data: userData } = useQuery<User>({
10+
queryKey: ["current-user"],
11+
queryFn: () => {
12+
return getCurrentUserDetails();
13+
},
14+
});
15+
16+
return (
17+
<div className="flex justify-between top-0 h-16 sm:h-20 bg-primary-300">
18+
<Link to="/" className="flex mx-4 my-auto text-lg">
19+
Playlist Manager
20+
</Link>
21+
<div className="flex mx-4">
22+
{userData ? (
23+
<UserMenu userData={userData} />
24+
) : (
25+
<button onClick={login}>Login</button>
26+
)}
27+
</div>
28+
</div>
29+
);
30+
};
31+
32+
export default Header;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React, { FC, ReactNode, useState } from "react";
2+
import DropdownMenu from "../../components/DropdownMenu";
3+
import { User } from "../../interfaces/User";
4+
import Button from "../../components/Button";
5+
import { logout } from "../../api";
6+
7+
interface UserMenuProps {
8+
userData: User;
9+
}
10+
11+
const UserMenu: FC<UserMenuProps> = ({userData}) => {
12+
const [isMenuOpen, setIsMenuOpen] = useState(false);
13+
return (
14+
<DropdownMenu isMenuOpen={isMenuOpen} closeMenu={() => setIsMenuOpen(false)} trigger={
15+
<button className="flex space-x-4" onClick={() => setIsMenuOpen((open) => !open)}>
16+
<div className="my-auto">{userData.display_name}</div>
17+
{userData.images && (
18+
<img
19+
src={userData.images[userData.images.length - 1].url}
20+
className="h-16 sm:h-20 rounded-full"
21+
></img>
22+
)}
23+
</button>
24+
}
25+
>
26+
<Button className={"w-full"} onClick={logout}>Logout</Button>
27+
</DropdownMenu>)
28+
}
29+
30+
export default UserMenu

frontend/src/presentational/Layout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from "react";
22
import { Outlet } from "react-router-dom";
33
import PlaybackFooter from "./PlaybackFooter";
4-
import Header from "./Header";
4+
import Header from "./Header/Header";
55

66
const Layout = () => {
77
return (

0 commit comments

Comments
 (0)