Fix some logic with login and redeeming

This commit is contained in:
Maximilian Harz 2025-06-26 00:01:30 +02:00
parent 2b3b3d5889
commit c0f4715bc8
6 changed files with 45 additions and 26 deletions

View File

@ -1,5 +1,4 @@
VITE_OPEN_COLLECTIVE_API_KEY=your_key
VITE_API_URL=https://api.utopia-lab.org
VITE_VALIDATE_INVITE_FLOW_ID=01d61db0-25aa-4bfa-bc24-c6a8f208a455
VITE_REDEEM_INVITE_FLOW_ID=todo

View File

@ -52,6 +52,7 @@ import { Landingpage } from './pages/Landingpage'
import MapContainer from './pages/MapContainer'
import { getBottomRoutes, routes } from './routes/sidebar'
import { InviteApi } from './api/InviteApi'
import { config } from '@/config'
const ProfileForm = lazy(() =>
import('utopia-ui/Profile').then((mod) => ({
@ -164,7 +165,7 @@ function App() {
assetsApi={new assetsApi('https://api.utopia-lab.org/assets/')}
appName={map.name}
embedded={embedded}
openCollectiveApiKey={import.meta.env.VITE_OPEN_COLLECTIVE_API_KEY}
openCollectiveApiKey={config.openCollectiveApiKey}
>
<Permissions
api={permissionsApiInstance}
@ -180,7 +181,7 @@ function App() {
<Routes>
<Route path='/*' element={<MapContainer map={map} layers={layers} />}>
<Route path='invite/:id' element={<InvitePage inviteApi={inviteApi} />} />
<Route path='login' element={<LoginPage />} />
<Route path='login' element={<LoginPage inviteApi={inviteApi} />} />
<Route path='signup' element={<SignupPage />} />
<Route
path='reset-password'

View File

@ -4,4 +4,5 @@ export const config = {
import.meta.env.VITE_VALIDATE_INVITE_FLOW_ID ?? '01d61db0-25aa-4bfa-bc24-c6a8f208a455',
),
redeemInviteFlowId: String(import.meta.env.VITE_REDEEM_INVITE_FLOW_ID ?? 'todo'),
openCollectiveApiKey: String(import.meta.env.VITE_OPEN_COLLECTIVE_API_KEY ?? ''),
}

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { Link, useNavigate } from 'react-router-dom'
import { toast } from 'react-toastify'
@ -6,10 +6,16 @@ import { MapOverlayPage } from '#components/Templates/MapOverlayPage'
import { useAuth } from './useAuth'
import type { InviteApi } from '#types/InviteApi'
interface Props {
inviteApi: InviteApi
}
/**
* @category Auth
*/
export function LoginPage() {
export function LoginPage({ inviteApi }: Props) {
const [email, setEmail] = useState<string>('')
const [password, setPassword] = useState<string>('')
@ -17,12 +23,26 @@ export function LoginPage() {
const navigate = useNavigate()
// eslint-disable-next-line react-hooks/exhaustive-deps
const onLogin = async () => {
const handleSuccess = useCallback(async () => {
const inviteCode = localStorage.getItem('inviteCode')
let invitingProfileId: string | null = null
if (inviteCode) {
// If an invite code is stored, redeem it
invitingProfileId = await inviteApi.redeemInvite(inviteCode)
localStorage.removeItem('inviteCode') // Clear invite code after redeeming
}
if (invitingProfileId) {
navigate(`/item/${invitingProfileId}`)
} else {
navigate('/')
}
}, [inviteApi, navigate])
const onLogin = useCallback(async () => {
await toast.promise(login({ email, password }), {
success: {
render({ data }) {
navigate('/')
void handleSuccess()
return `Hi ${data?.first_name ? data.first_name : 'Traveler'}`
},
// other options
@ -36,7 +56,7 @@ export function LoginPage() {
},
pending: 'logging in ...',
})
}
}, [email, handleSuccess, login, password])
useEffect(() => {
const keyDownHandler = (event: KeyboardEvent) => {

View File

@ -1,5 +1,4 @@
import { createContext, useState, useContext, useEffect, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import type { InviteApi } from '#types/InviteApi'
import type { UserApi } from '#types/UserApi'
@ -22,6 +21,7 @@ interface AuthCredentials {
interface AuthContextProps {
isAuthenticated: boolean
isInitialized: boolean
user: UserItem | null
login: (credentials: AuthCredentials) => Promise<UserItem | undefined>
register: (credentials: AuthCredentials, userName: string) => Promise<UserItem | undefined>
@ -35,6 +35,7 @@ interface AuthContextProps {
const AuthContext = createContext<AuthContextProps>({
isAuthenticated: false,
isInitialized: false,
user: null,
login: () => Promise.reject(Error('Unimplemented')),
register: () => Promise.reject(Error('Unimplemented')),
@ -49,11 +50,11 @@ const AuthContext = createContext<AuthContextProps>({
/**
* @category Auth
*/
export const AuthProvider = ({ userApi, inviteApi, children }: AuthProviderProps) => {
const navigate = useNavigate()
export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
const [user, setUser] = useState<UserItem | null>(null)
const [token, setToken] = useState<string>()
const [loading, setLoading] = useState<boolean>(true)
const [loading, setLoading] = useState<boolean>(false)
const [isInitialized, setIsInitialized] = useState<boolean>(false)
const isAuthenticated = !!user
const loadUser: () => Promise<UserItem | undefined> = useCallback(async () => {
@ -65,11 +66,15 @@ export const AuthProvider = ({ userApi, inviteApi, children }: AuthProviderProps
setUser(me)
setLoading(false)
return me
} else return undefined
} else {
return undefined
}
// eslint-disable-next-line no-catch-all/no-catch-all
} catch (error) {
setLoading(false)
return undefined
} finally {
setIsInitialized(true)
}
}, [userApi])
@ -83,15 +88,7 @@ export const AuthProvider = ({ userApi, inviteApi, children }: AuthProviderProps
const user = await userApi.login(credentials.email, credentials.password)
setToken(user?.access_token)
const fullUser = await loadUser()
const inviteCode = localStorage.getItem('inviteCode')
if (inviteCode) {
// If an invite code is stored, redeem it
const invitingProfileId = await inviteApi.redeemInvite(inviteCode)
localStorage.removeItem('inviteCode') // Clear invite code after redeeming
if (invitingProfileId) {
navigate(`/item/${invitingProfileId}`)
}
}
return fullUser
} catch (error) {
setLoading(false)
@ -163,6 +160,7 @@ export const AuthProvider = ({ userApi, inviteApi, children }: AuthProviderProps
<AuthContext.Provider
value={{
isAuthenticated,
isInitialized,
user,
login,
register,

View File

@ -15,7 +15,7 @@ interface Props {
* @category Onboarding
*/
export function InvitePage({ inviteApi }: Props) {
const { isAuthenticated, loading: isLoadingAuthentication } = useAuth()
const { isAuthenticated, isInitialized: isAuthenticationInitialized } = useAuth()
const { id } = useParams<{ id: string }>()
const navigate = useNavigate()
@ -35,7 +35,7 @@ export function InvitePage({ inviteApi }: Props) {
}
}
if (isLoadingAuthentication) return
if (!isAuthenticationInitialized) return
if (isAuthenticated) {
void redeemInvite()
@ -46,7 +46,7 @@ export function InvitePage({ inviteApi }: Props) {
// Redirect to login page
navigate('/login')
}
}, [id, isAuthenticated, inviteApi, navigate, isLoadingAuthentication])
}, [id, isAuthenticated, inviteApi, navigate, isAuthenticationInitialized])
return (
<MapOverlayPage backdrop className='tw:max-w-xs tw:h-fit'>