commit 68031fcdd01b7366a33bc527670a68ce12781fb7
parent c7e44fcae0065fb38b68fc3faae5d8c6e7789de8
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date: Sun, 15 Sep 2024 22:34:52 +0200
feat(magic): add AuthVerify comp & redo session
Diffstat:
7 files changed, 79 insertions(+), 34 deletions(-)
diff --git a/src/App.jsx b/src/App.jsx
@@ -2,18 +2,21 @@ import './App.css'
import { Suspense, lazy, useState, useEffect, Fragment } from 'react'
import { BrowserRouter as Router, Routes, Route, Outlet, Link, NavLink } from 'react-router-dom'
import { supabase } from './supabaseClient'
-
+import { CookiesProvider, useCookies } from 'react-cookie'
const Score = lazy(() => import('./Score'))
const Heats = lazy(() => import('./Heats'))
const Athletes = lazy(() => import('./Athletes'))
const Startlist = lazy(() => import('./Startlist'))
const Auth = lazy(() => import('./Auth'))
+const AuthVerify = lazy(() => import('./AuthVerify'))
const Leaderboard = lazy(() => import('./Leaderboard'))
document.title = import.meta.env.VITE_APP_DOC_TITLE ? import.meta.env.VITE_APP_DOC_TITLE : 'My Heats'
-function Layout({session}) {
+function Layout() {
+ const [session, setSession, destroySession] = useCookies(['auth'])
+
return (
<Fragment>
<nav>
@@ -25,21 +28,21 @@ function Layout({session}) {
Leaderboard
</NavLink>
</li>
- {session ? <li>
+ {session.auth ? <li>
<NavLink
className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
to="/score">
Scoring
</NavLink>
</li> : ''}
- {session ? <li>
+ {session.auth ? <li>
<NavLink
className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
to="/heats">
Heats and Startlists
</NavLink>
</li> : ''}
- {session ? <li>
+ {session.auth ? <li>
<NavLink
className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
to="/athletes">
@@ -55,7 +58,7 @@ function Layout({session}) {
<br />
<span className='version'>MyHeats <a href="https://code.in0rdr.ch/myheats/refs.html">v0.5-nightly</a></span>
<span className='login'>
- {session ? <button onClick={() => supabase.auth.signOut()}> Sign out {session.user.email} </button> :
+ {session.auth ? <button onClick={() => destroySession('auth')}>Sign out {session.auth.email}</button> :
<NavLink
className={({ isActive, isPending }) => isPending ? "pending" : isActive ? "active" : ""}
to="/auth">
@@ -77,18 +80,7 @@ function NoMatch() {
}
function App() {
- const [session, setSession] = useState(null)
-
- useEffect(() => {
- // Returns the session, refreshing it if necessary
- supabase.auth.getSession().then(({ data: { session } }) => {
- setSession(session)
- })
-
- supabase.auth.onAuthStateChange((_event, session) => {
- setSession(session)
- })
- }, [])
+ const [session] = useCookies(['auth'])
return (
<Fragment>
@@ -102,6 +94,7 @@ function App() {
<Route path="/athletes" element={<Athletes session={session} />} />
<Route path="/startlist/:heatId" element={<Startlist session={session} />} />
<Route path="/auth" element={<Auth />} />
+ <Route path="/authverify" element={<AuthVerify />} />
<Route path="*" element={<NoMatch />} />
</Route>
</Routes>
diff --git a/src/Athletes.jsx b/src/Athletes.jsx
@@ -129,7 +129,7 @@ function AthleteForm({session}) {
function Athletes({session}) {
return (
<div>
- {!session ? <Auth /> : <AthleteForm session={session} />}
+ {!session.auth ? <Auth /> : <AthleteForm session={session} />}
</div>
)
}
diff --git a/src/Auth.jsx b/src/Auth.jsx
@@ -1,5 +1,7 @@
import { useState } from 'react'
-import { supabase } from './supabaseClient'
+
+const api_uri = import.meta.env.MYHEATS_API ? import.meta.env.MYHEATS_API: 'http://127.0.0.1'
+const api_port = import.meta.env.MYHEATS_API_PORT ? import.meta.env.MYHEATS_API_PORT: '8000'
function Auth() {
const [loading, setLoading] = useState(false)
@@ -10,14 +12,19 @@ function Auth() {
try {
setLoading(true)
- const { error } = await supabase.auth.signInWithOtp({
- email: email,
- options: {
- shouldCreateUser: false,
- emailRedirectTo: window.location.origin
- }})
- if (error) throw error
- alert('Check your email for the login link!')
+
+ const response = await fetch(`${api_uri}:${api_port}/v1/auth/requestMagicLink`, {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({"email": email}),
+ })
+
+ const { data, error } = await response.json()
+ if (error) {
+ alert(error)
+ } else {
+ alert('Check your email for the login link!')
+ }
} catch (error) {
alert(error.error_description || error.message)
} finally {
diff --git a/src/AuthVerify.jsx b/src/AuthVerify.jsx
@@ -0,0 +1,45 @@
+import { useState, useEffect } from 'react'
+import { CookiesProvider, useCookies } from 'react-cookie'
+
+const api_uri = import.meta.env.MYHEATS_API ? import.meta.env.MYHEATS_API: 'http://127.0.0.1'
+const api_port = import.meta.env.MYHEATS_API_PORT ? import.meta.env.MYHEATS_API_PORT: '8000'
+
+function AuthVerify() {
+ const [loading, setLoading] = useState(true)
+ const [session, setSession, removeSession] = useCookies(['user'])
+ const [response, setResponse] = useState({})
+ const queryParameters = new URLSearchParams(window.location.search)
+ const token = queryParameters.get("token")
+
+ useEffect(() => {
+ (async () => {
+ // check token validity on API endpoint
+ const response = await fetch(`${api_uri}:${api_port}/v1/auth/verify?token=${token}`)
+ const { message, data, error } = await response.json()
+ setResponse(data)
+ console.log("Uncle roger be like:", message)
+
+ if (error) {
+ alert(error)
+ } else {
+ // Set client session (https://www.npmjs.com/package/react-cookie)
+ setSession('auth', data, {
+ path: '/',
+ secure: process.env.NODE_ENV !== 'development',
+ // todo: reuses Token expiration, this could be different
+ expires: new Date(data.expires_at),
+ })
+
+ // redirect if everything ok
+ window.location.replace('/');
+ }
+ })();
+ }, []);
+
+ return (
+ <div className="AuthVerify">
+ </div>
+ )
+}
+
+export default AuthVerify;
diff --git a/src/Heats.jsx b/src/Heats.jsx
@@ -119,7 +119,7 @@ function HeatForm({session}) {
function Heats({session}) {
return (
<div>
- {!session ? <Auth /> : <HeatForm session={session} />}
+ {!session.auth ? <Auth /> : <HeatForm session={session} />}
</div>
)
}
diff --git a/src/Score.jsx b/src/Score.jsx
@@ -59,7 +59,7 @@ function ScoringForm({session}) {
const currentScore = await supabase.from('scores').select()
.eq('heat', heatSelection.value)
.eq('athlete', athleteSelection.value)
- .eq('judge', session.user.id)
+ .eq('judge', session.id)
if (score === 0 && currentScore.data?.length > 0) {
// fallback to current score when no new scoring took place
@@ -69,11 +69,11 @@ function ScoringForm({session}) {
updateScore(score,
heatSelection.value,
athleteSelection.value,
- session.user.id)
+ session.id)
}
setLoading(false)
})();
- }, [heatSelection, athleteSelection, session.user.id, score]);
+ }, [heatSelection, athleteSelection, session.id, score]);
return (
<div>
@@ -118,7 +118,7 @@ function ScoringForm({session}) {
function Score({session}) {
return (
<div>
- {!session ? <Auth /> : <ScoringForm session={session} />}
+ {!session.auth ? <Auth /> : <ScoringForm session={session} />}
</div>
)
}
diff --git a/src/Startlist.jsx b/src/Startlist.jsx
@@ -150,7 +150,7 @@ function Startlist({session}) {
return (
<div>
- {!session ? <Auth /> : <StartlistForm heatId={heatId} />}
+ {!session.auth ? <Auth /> : <StartlistForm heatId={heatId} />}
</div>
)
}