diff --git a/README.md b/README.md index c0c3693..e1af99e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CRUD Database App -This code runs the CRUD Database application for volunteers to interact with, input, and update datbase information relating to the Parking Lot Map and Parking Reform Map. +This code runs the CRUD Database application for volunteers to interact with, input, and update database information relating to the Parking Lot Map and Parking Reform Map. This project utilizes React, Typescript, and Parcel. diff --git a/crud-frontend/.eslintrc.json b/crud-frontend/.eslintrc.json index 9a59abc..dea0296 100644 --- a/crud-frontend/.eslintrc.json +++ b/crud-frontend/.eslintrc.json @@ -14,7 +14,10 @@ "sourceType": "module" }, "plugins": ["@typescript-eslint", "react"], - "rules": {}, + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "off" + }, "settings": { "react": { "version": "detect" // Automatically detect the React version diff --git a/crud-frontend/.gitignore b/crud-frontend/.gitignore index b77bf3e..1f62131 100644 --- a/crud-frontend/.gitignore +++ b/crud-frontend/.gitignore @@ -35,3 +35,6 @@ yarn-error.log* # Ignore cache files /.changelog /.cache + +# Environment files +.env diff --git a/crud-frontend/package.json b/crud-frontend/package.json index 16ec7ad..0b21e17 100644 --- a/crud-frontend/package.json +++ b/crud-frontend/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "start": "set PORT=3000 && parcel ./src/index.html", - "build": "rm -rf dist; parcel build --detailed-report", + "start": "parcel ./src/index.html --port 3000", + "build": "rm -rf dist; parcel build ./src/index.html", "test": "echo \"Error: no test specified\" && exit 1", "fmt": "prettier --write .", "fix": "prettier --write .; eslint --fix .", @@ -30,12 +30,15 @@ "@mui/icons-material": "^5.14.16", "@mui/material": "^5.14.17", "@react-oauth/google": "^0.11.1", + "@supabase/supabase-js": "^2.39.3", "@types/react": "^18.2.33", "@types/react-dom": "^18.2.14", + "axios": "^1.6.1", "deasync": "^0.1.29", + "dotenv": "^16.3.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react-router-dom": "^6.20.0" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.10.0", diff --git a/crud-frontend/src/App.tsx b/crud-frontend/src/App.tsx index 7e7494e..af3ac3a 100644 --- a/crud-frontend/src/App.tsx +++ b/crud-frontend/src/App.tsx @@ -1,17 +1,21 @@ import React, { lazy, Suspense, FC } from "react"; import { Route, Routes, BrowserRouter } from "react-router-dom"; - -const HomePage = lazy(() => import("./pages/home/home")); +import LoggedIn from "./pages/loggedIn/loggedIn"; +import HomePage from "./pages/home/home" +import { AuthProvider } from "./contexts/AuthContext"; const AppRoutes: FC = () => { return ( - - Loading...}> - - } /> - - - + + + Loading...}> + + } /> + } /> + + + + ); }; diff --git a/crud-frontend/src/components/login/login.tsx b/crud-frontend/src/components/login/login.tsx new file mode 100644 index 0000000..04d65e8 --- /dev/null +++ b/crud-frontend/src/components/login/login.tsx @@ -0,0 +1,76 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import { createClient } from "@supabase/supabase-js"; + +const supabase = createClient(process.env.REACT_APP_SUPABASE_URL, process.env.REACT_APP_SUPABASE_PUBLIC_ANON_KEY, { + auth: { + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: true + } +}); + +... +declare global { + interface Window { + handleSignInWithGoogle?: any; + } +} + +function Login() { + const [session, setSession] = useState(null); + const [user, setUser] = useState(null); + + useEffect(() => { + const script = document.createElement('script'); + + script.src = "https://accounts.google.com/gsi/client"; + script.async = true; + script.defer = true; + + document.body.appendChild(script); + + return () => { + document.body.removeChild(script); + } + }, []); + + window.handleSignInWithGoogle = async(response) => { + try { + const { data, error } = await supabase.auth.signInWithOAuth({ + provider: 'google', + options: { + redirectTo: 'http://localhost:3000/logged-in' + }, + }) + + } catch (error) { + console.log("Error:", error); + } + } + + return ( + <> +
+
+
+ +
+
+
+ + ); +} + +export default Login; \ No newline at end of file diff --git a/crud-frontend/src/contexts/AuthContext.tsx b/crud-frontend/src/contexts/AuthContext.tsx new file mode 100644 index 0000000..25ac97c --- /dev/null +++ b/crud-frontend/src/contexts/AuthContext.tsx @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import React, { createContext, useState, useEffect, useContext, ReactNode } from "react"; +import { createClient, Session, AuthChangeEvent } from "@supabase/supabase-js"; + +const supabase = createClient(process.env.REACT_APP_SUPABASE_URL, process.env.REACT_APP_SUPABASE_PUBLIC_ANON_KEY, { + auth: { + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: true + } +}); + +interface AuthContextProps { + session: Session | null; + user: any; +} +const AuthContext = createContext({session: null, user: null}); + +export const useAuth = () => useContext(AuthContext) + +export const AuthProvider: React.FC<{ children: ReactNode }> = ({children}) => { + const [session, setSession] = useState(null); + const [user, setUser] = useState(null); + + useEffect(() => { + const sessionListener = supabase.auth.onAuthStateChange((event: AuthChangeEvent, session: Session | null) => { + setSession(session); + setUser(session?.user ?? null); + }); + + const fetchSession = async() => { + try { + const { data, error } = await supabase.auth.getSession(); + + if(error) { + // Handle error if any + console.error("Error fetching session: ", error) + } else if (data && data.session) { + // session exists + setSession(data.session); + setUser(data.session.user ?? null); + } else { + // no session exists, set seesion state to null + setSession(null); + setUser(null); + } + } catch (error) { + console.error("Unexpected error fetching session: ", error) + } + }; + + fetchSession(); + + return () => { + if(sessionListener.data.subscription) { + sessionListener.data.subscription.unsubscribe(); + } + }; + }, []); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/crud-frontend/src/index.html b/crud-frontend/src/index.html index 0e8b392..2d9f365 100644 --- a/crud-frontend/src/index.html +++ b/crud-frontend/src/index.html @@ -7,5 +7,6 @@
+ diff --git a/crud-frontend/src/pages/home/home.tsx b/crud-frontend/src/pages/home/home.tsx index 1e5ebee..3367659 100644 --- a/crud-frontend/src/pages/home/home.tsx +++ b/crud-frontend/src/pages/home/home.tsx @@ -7,6 +7,7 @@ import Grid from "@mui/material/Grid"; import Typography from "@mui/material/Typography"; import PRN_LOGO from "../../../public/imgs/PRN_logo.png"; import "./home.scss"; +import Login from "./../../components/login/login"; export default function HomePage() { return ( @@ -33,6 +34,7 @@ export default function HomePage() { Sign in + diff --git a/crud-frontend/src/pages/loggedIn/loggedIn.tsx b/crud-frontend/src/pages/loggedIn/loggedIn.tsx new file mode 100644 index 0000000..11a248b --- /dev/null +++ b/crud-frontend/src/pages/loggedIn/loggedIn.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useAuth } from "../../contexts/AuthContext"; +import { supabase } from "../../setup/supabaseSetup"; +import { useNavigate } from "react-router-dom"; + + +export default function LoggedIn() { + const {session, user} = useAuth(); + + const navigate = useNavigate(); + + const signOut = async() => { + try { + const { error } = await supabase.auth.signOut(); + if(error) { + console.error("Error signing out: ", error.message) + } else { + console.log("User signed out successfully"); + navigate("/"); + } + } catch (error) { + console.error("Error while signing out: ", error) + } + } + + return ( +
+ {session ?

User is logged in

:

User is not logged in

} + {user ?

User {user.email} is logged in

:

User has no name

} + +
+ ) +} + diff --git a/crud-frontend/src/pages/rootpage/root.tsx b/crud-frontend/src/pages/rootpage/root.tsx index a024432..176bf05 100644 --- a/crud-frontend/src/pages/rootpage/root.tsx +++ b/crud-frontend/src/pages/rootpage/root.tsx @@ -8,9 +8,7 @@ interface RootpageProps { const Rootpage: FC = ({ children, header }) => { return ( <> - {/* Have a header here */} {children} - {/* Have a footer here */} ); }; diff --git a/crud-frontend/src/setup/supabaseSetup.tsx b/crud-frontend/src/setup/supabaseSetup.tsx new file mode 100644 index 0000000..a38a14d --- /dev/null +++ b/crud-frontend/src/setup/supabaseSetup.tsx @@ -0,0 +1,10 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = process.env.REACT_APP_SUPABASE_URL; +const supabaseKey = process.env.REACT_APP_SUPABASE_PUBLIC_ANON_KEY; + +if (!supabaseUrl || !supabaseKey) { + throw new Error("Supabase URL or Supabase Key is missing. Make sure to set them in your environment variables."); +} + +export const supabase = createClient(supabaseUrl, supabaseKey); diff --git a/crud-frontend/tsconfig.json b/crud-frontend/tsconfig.json index 3e676c9..9465f8d 100644 --- a/crud-frontend/tsconfig.json +++ b/crud-frontend/tsconfig.json @@ -2,21 +2,14 @@ "compilerOptions": { /* Language and Environment */ "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - - /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, - - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - - /* Type Checking */ - "strict": true /* Enable all strict type-checking options. */, - /* Completeness */ "skipLibCheck": true /* Skip type checking all .d.ts files. */, "moduleResolution": "node", "lib": ["DOM", "ESNext"], - "jsx": "react-jsx" + "jsx": "react-jsx", + "noUnusedLocals": false, + "allowSyntheticDefaultImports": true, + "module": "esnext" } }