diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e3483a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,25 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +charset = utf-8 + +indent_style = space +indent_size = 2 + +trim_trailing_whitespace = true +max_line_length = 80 + +[*.txt] +indent_style = tab +indent_size = 4 + +[*.{diff,md}] +trim_trailing_whitespace = false diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 79533a8..0000000 --- a/src/App.css +++ /dev/null @@ -1,9 +0,0 @@ -.App { - min-height: 100vh; - display: flex; - text-align: center; - align-items: center; - justify-content: center; - background-color: #282c34; - color: white; -} diff --git a/src/App.js b/src/App.js index a448e7e..5c5db5d 100644 --- a/src/App.js +++ b/src/App.js @@ -4,7 +4,7 @@ import { BrowserRouter } from "react-router-dom"; import { ToastContainer } from "react-toastify"; // Styles -import './App.css'; +import styles from './App.module.css'; // Assets @@ -94,11 +94,13 @@ function App() { return ( -
+
- +
+ +
{ try { diff --git a/src/api/axiosInstance.js b/src/api/axiosInstance.js index 2750970..dd5428d 100644 --- a/src/api/axiosInstance.js +++ b/src/api/axiosInstance.js @@ -1,7 +1,8 @@ import axios from "axios"; +import { backendDomain } from "./paths"; export const axiosInstance = axios.create({ - baseURL: process.env.REACT_APP_API_BASE_URL, + baseURL: backendDomain, withCredentials: true, timeout: 20000, headers: { diff --git a/src/api/paths.js b/src/api/paths.js new file mode 100644 index 0000000..3367bcc --- /dev/null +++ b/src/api/paths.js @@ -0,0 +1,7 @@ +export const backendDomain = process.env.REACT_APP_API_BASE_URL + "/"; + +export const authLoginURL = backendDomain + "api/auth/login" +export const authLogoutURL = backendDomain + "api/auth/logout" + +export const authHealthCheckURL = "auth-health"; +export const authRefreshURL = "api/auth/refresh"; diff --git a/src/assets/icons/logo.svg b/src/assets/icons/logo.svg index 92ce7fd..82484b2 100644 --- a/src/assets/icons/logo.svg +++ b/src/assets/icons/logo.svg @@ -1,13 +1,14 @@ - + + width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000" + preserveAspectRatio="xMidYMid meet"> - - + - +307 3 562 7 565 3 4 260 7 570 7 l563 0 0 -565z" /> + diff --git a/src/components/AnimatedSVG/AnimatedSVG.module.css b/src/components/AnimatedSVG/AnimatedSVG.module.css new file mode 100644 index 0000000..bc3a88c --- /dev/null +++ b/src/components/AnimatedSVG/AnimatedSVG.module.css @@ -0,0 +1,20 @@ +/* the value 54150 is decided by getting the length of the path (54122 for the logo asset) */ + +.svgWrapper path { + stroke-dasharray: 54150; + stroke-dashoffset: 54150; + animation: draw 5s ease-in-out infinite; + animation-fill-mode: both; +} + +@keyframes draw { + 0% { + stroke-dashoffset: 54150; + } + 60% { + stroke-dashoffset: 0; + } + 100% { + stroke-dashoffset: 54150; + } +} diff --git a/src/components/AnimatedSVG/index.jsx b/src/components/AnimatedSVG/index.jsx new file mode 100644 index 0000000..3e0683c --- /dev/null +++ b/src/components/AnimatedSVG/index.jsx @@ -0,0 +1,45 @@ +import React from 'react'; +import styles from "./AnimatedSVG.module.css"; + +const AnimatedSVG = ({ stroke = "#ffffff" }) => { + return ( +
+ {/* width, height and viewBox are necessary */} + + + + + +
+ ) +} + +export default AnimatedSVG; diff --git a/src/components/Navbar/Navbar.module.css b/src/components/Navbar/Navbar.module.css index 45d8505..802edee 100644 --- a/src/components/Navbar/Navbar.module.css +++ b/src/components/Navbar/Navbar.module.css @@ -2,7 +2,7 @@ display: flex; flex-direction: column; align-items: center; - justify-content: space-evenly; + justify-content: space-around; position: sticky; top: 0; left: 0; diff --git a/src/components/Navbar/index.jsx b/src/components/Navbar/index.jsx index b250c7d..1794041 100644 --- a/src/components/Navbar/index.jsx +++ b/src/components/Navbar/index.jsx @@ -1,16 +1,25 @@ -import React from 'react' +import React, { useContext } from 'react' + import styles from "./Navbar.module.css"; -import { NavLink } from 'react-router-dom'; + +import { AuthContext } from "../../App"; +import StyledNavLink from '../StyledNavLink'; const Navbar = () => { + const auth = useContext(AuthContext); + return ( ) } -export default Navbar \ No newline at end of file +export default Navbar diff --git a/src/components/StyledNavLink/StyledNavLink.module.css b/src/components/StyledNavLink/StyledNavLink.module.css new file mode 100644 index 0000000..81ec8c1 --- /dev/null +++ b/src/components/StyledNavLink/StyledNavLink.module.css @@ -0,0 +1,12 @@ +.link_wrapper { + padding: 2em; + border-radius: 20%; +} + +.active_link { + +} + +.inactive_link { + +} diff --git a/src/components/StyledNavLink/index.jsx b/src/components/StyledNavLink/index.jsx new file mode 100644 index 0000000..f47dc57 --- /dev/null +++ b/src/components/StyledNavLink/index.jsx @@ -0,0 +1,33 @@ +import React from 'react' +import { NavLink } from 'react-router-dom'; + +import styles from "./StyledNavLink.module.css"; + +/** + * @param {{ + * path: string, + * text: string, + * activeClass: string, + * inactiveClass: string + * }} + * @returns + */ +const StyledNavLink = ({ + path = "/", + text = "Go To", + activeClass = styles.active_link, + inactiveClass = styles.inactive_link +}) => { + return ( +
+ isActive ? activeClass : inactiveClass} + > + {text} + +
+ ) +} + +export default StyledNavLink; diff --git a/src/index.css b/src/index.css index ec2585e..e32d596 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,110 @@ -body { +/*===== CSS GLOBAL VARIABLES =====*/ + +/* Font Size */ +:root { + --normalFontSize: 16px; + --headingFont: AileronFont; + --primaryFont: AileronFont; + --text: whitesmoke; + --bgBlue: #040a53; + --darkBlue: #061320; + --vscBlue: #1b81a8; + --dYellow: #5b3c15; + --lYellow: #fcbe72; + + --primary: #04005e; + --secondry: #440bd4; + --tertiary: #ff2079; + --accent: #e92efb; + --bg: #262626; + --blue1: #405de6; + --blue2: #5851d8; + --purple1: #833ab4; + --pink1: #c13584; + --pink2: #e1306c; + --red1: #fd1d1d; + --orange1: #f56040; + --orange2: #f77737; + --orange3: #fcaf45; + --orange4: #ffdc80; + + --fun_b: #b00b69; + --fun_d: #1d1cc5; +} + +/* Colors */ + +/* Fonts */ +@font-face { + font-family: AileronFont; + src: url(/src/assets/fonts/Aileron-Black.otf); + font-weight: bold; +} +@font-face { + font-family: AileronFont; + src: url(/src/assets/fonts/Aileron-Light.otf); + font-weight: light; +} + +/* rem-based (root element) sizes */ +:root { + --mb-1: 0.5rem; + --mb-2: 1rem; + --mb-3: 1.5rem; + --mb-4: 2rem; + --mb-5: 2.5rem; + --mb-6: 3rem; +} + +/*===== z-index =====*/ +:root { + --z-back: -10; + --z-normal: 1; + --z-tooltip: 10; + --z-fixed: 100; +} + +/*===== BASE =====*/ +*, +::before, +::after { + box-sizing: border-box; +} + +* { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + padding: 0; +} + +html { + scroll-behavior: smooth; +} + +ul { + padding: 0; + list-style: outside; +} + +img { + max-width: 100%; + height: auto; + display: block; +} + +body { + font-family: var(--primaryFont),-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", + "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + font-size: var(--normalFontSize); + color: var(--text); + background-color: var(--bgBlue); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; + letter-spacing: 1.5px; } code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } diff --git a/src/pages/Graph/Graph.module.css b/src/pages/Graph/Graph.module.css index d39838f..db5ac9e 100644 --- a/src/pages/Graph/Graph.module.css +++ b/src/pages/Graph/Graph.module.css @@ -1,6 +1,5 @@ .graph_wrapper { display: flex; - position: inherit; width: 100%; - height: 80vh; + height: 100vh; } \ No newline at end of file diff --git a/src/pages/Landing/Landing.module.css b/src/pages/Landing/Landing.module.css index 1396f21..98c2597 100644 --- a/src/pages/Landing/Landing.module.css +++ b/src/pages/Landing/Landing.module.css @@ -10,18 +10,3 @@ height: 40vmin; pointer-events: none; } - -@media (prefers-reduced-motion: no-preference) { - .app_logo { - animation: App-logo-spin infinite 20s linear; - } -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/pages/Landing/index.jsx b/src/pages/Landing/index.jsx index f797cd1..4cffbce 100644 --- a/src/pages/Landing/index.jsx +++ b/src/pages/Landing/index.jsx @@ -2,27 +2,33 @@ import React, { useEffect } from "react" import { useSearchParams } from "react-router-dom"; import styles from "./Landing.module.css" import logo from '../../assets/icons/logo.svg'; -import { showSuccessToastNotification } from "../../components/ToastNotification"; +import { showInfoToastNotification, showSuccessToastNotification } from "../../components/ToastNotification"; +import AnimatedSVG from "../../components/AnimatedSVG"; const Landing = () => { - // eslint-disable-next-line no-unused-vars - const [searchParams, setSearchParams] = useSearchParams(); - useEffect(() => { - if (searchParams.get("login") === "success") { - showSuccessToastNotification("Logged in!"); - } - }, [searchParams]); - return ( -
- logo -

Organize your Spotify playlists as a graph.

-
Features:
-
    -
  • blah 1
  • -
-
- ) + // eslint-disable-next-line no-unused-vars + const [searchParams, setSearchParams] = useSearchParams(); + useEffect(() => { + if (searchParams.get("login") === "success") { + showSuccessToastNotification("Logged in!"); + } else if (searchParams.get("logout") === "success") { + showInfoToastNotification("Logged out."); + } + }, [searchParams]); + return ( + <> +
+ +

organize your Spotify playlists as a graph

+
+
    +
  • DAG graph of your playlists
  • +
  • Link them to sync tracks
  • +
  • Periodic syncing
  • +
+ + ) } -export default Landing \ No newline at end of file +export default Landing diff --git a/src/pages/Login/index.jsx b/src/pages/Login/index.jsx index 275e571..3933c81 100644 --- a/src/pages/Login/index.jsx +++ b/src/pages/Login/index.jsx @@ -1,17 +1,20 @@ import React, { useEffect } from 'react'; import styles from './Login.module.css'; +import { authLoginURL } from '../../api/paths'; // auth through backend const Login = () => { useEffect(() => { const timeoutID = setTimeout(() => { - window.open(process.env.REACT_APP_API_BASE_URL + "/api/auth/login", "_self") + window.open(authLoginURL, "_self") }, 1000); return () => clearTimeout(timeoutID); }, []); return ( -
Redirecting to Spotify...
+
+ Redirecting to Spotify... +
) } diff --git a/src/pages/Logout/Logout.module.css b/src/pages/Logout/Logout.module.css new file mode 100644 index 0000000..941402a --- /dev/null +++ b/src/pages/Logout/Logout.module.css @@ -0,0 +1,6 @@ +.logout_wrapper { + display: flex; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); +} diff --git a/src/pages/Logout/index.jsx b/src/pages/Logout/index.jsx new file mode 100644 index 0000000..7e5df57 --- /dev/null +++ b/src/pages/Logout/index.jsx @@ -0,0 +1,20 @@ +import React, { useEffect } from 'react'; +import styles from './Logout.module.css'; +import { authLogoutURL } from '../../api/paths'; + +const Logout = () => { + useEffect(() => { + const timeoutID = setTimeout(() => { + window.open(authLogoutURL, "_self") + }, 1000); + + return () => clearTimeout(timeoutID); + }, []); + return ( +
+ See you soon! +
+ ) +} + +export default Logout; \ No newline at end of file diff --git a/src/routes/AllRoutes.jsx b/src/routes/AllRoutes.jsx index f2471db..b3cab28 100644 --- a/src/routes/AllRoutes.jsx +++ b/src/routes/AllRoutes.jsx @@ -7,12 +7,14 @@ import Landing from "../pages/Landing"; import PageNotFound from "../pages/PageNotFound"; import Graph from "../pages/Graph"; import Login from "../pages/Login"; +import Logout from "../pages/Logout"; const AllRoutes = () => { return ( {/* Routes that require user to be logged in */} }> + } /> } /> {/* } /> */} @@ -25,7 +27,6 @@ const AllRoutes = () => { {/* Common routes */} } /> - } /> {/* 404 */} } /> } />