mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
Show dialog to accept following
This commit is contained in:
parent
293001b721
commit
68fb201907
@ -3,7 +3,7 @@ import { MapContainer } from 'react-leaflet'
|
||||
|
||||
import { ContextWrapper } from '#components/AppShell/ContextWrapper'
|
||||
|
||||
import { useRedeemInvite } from './hooks/useRedeemInvite'
|
||||
import { useStoredInviteCode } from './hooks/useStoredInviteCode'
|
||||
import { UtopiaMapInner } from './UtopiaMapInner'
|
||||
|
||||
import type { InviteApi } from '#types/InviteApi'
|
||||
@ -112,8 +112,8 @@ function UtopiaMap({
|
||||
/** tile size (default 256) */
|
||||
tileSize?: number
|
||||
}) {
|
||||
// Check for invite code in localStorage and loaded profile, redeem if possible
|
||||
useRedeemInvite(inviteApi)
|
||||
// Check for invite code in localStorage
|
||||
useStoredInviteCode()
|
||||
return (
|
||||
<ContextWrapper>
|
||||
<MapContainer
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
import { useAuth } from '#components/Auth/useAuth'
|
||||
|
||||
import { useItems } from './useItems'
|
||||
import { useItems, useAddItem } from './useItems'
|
||||
import { useLayers } from './useLayers'
|
||||
|
||||
export const useMyProfile = () => {
|
||||
const items = useItems()
|
||||
const { user } = useAuth()
|
||||
const layers = useLayers()
|
||||
const addItem = useAddItem()
|
||||
|
||||
// Find the user's profile item
|
||||
const myProfile = items.find(
|
||||
@ -16,5 +19,31 @@ export const useMyProfile = () => {
|
||||
// allItemsLoaded is not reliable
|
||||
const isMyProfileLoaded = isAnyUserProfileLoaded && !!user
|
||||
|
||||
return { myProfile, isMyProfileLoaded }
|
||||
const createEmptyProfile = async () => {
|
||||
if (!user) return
|
||||
|
||||
const userLayer = layers.find((l) => l.userProfileLayer === true)
|
||||
if (!userLayer?.api?.createItem) {
|
||||
throw new Error('User profile layer or create API not available')
|
||||
}
|
||||
|
||||
const newProfile = {
|
||||
id: crypto.randomUUID(),
|
||||
name: user.first_name ?? 'User',
|
||||
}
|
||||
|
||||
const result = await userLayer.api.createItem(newProfile)
|
||||
|
||||
// Use server response for local state update
|
||||
addItem({
|
||||
...result,
|
||||
user_created: user,
|
||||
layer: userLayer,
|
||||
public_edit: false,
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
return { myProfile, isMyProfileLoaded, createEmptyProfile }
|
||||
}
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { useMyProfile } from './useMyProfile'
|
||||
|
||||
import type { InviteApi } from '#types/InviteApi'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const useRedeemInvite = (inviteApi: InviteApi) => {
|
||||
const inviteCode = localStorage.getItem('inviteCode')
|
||||
|
||||
const { myProfile } = useMyProfile()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [isRedeemingDone, setRedeemingDone] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
async function redeemInvite(inviteCode: string, myProfile: Item) {
|
||||
const invitingProfileId = await inviteApi.redeemInvite(inviteCode, myProfile.id)
|
||||
|
||||
if (invitingProfileId) {
|
||||
toast.success('Invite redeemed successfully!')
|
||||
localStorage.removeItem('inviteCode')
|
||||
navigate(`/item/${invitingProfileId}`)
|
||||
} else {
|
||||
toast.error('Failed to redeem invite')
|
||||
}
|
||||
}
|
||||
|
||||
if (!inviteCode || !myProfile || isRedeemingDone) return
|
||||
void redeemInvite(inviteCode, myProfile)
|
||||
setRedeemingDone(true)
|
||||
}, [inviteApi, inviteCode, isRedeemingDone, myProfile, navigate])
|
||||
}
|
||||
17
lib/src/Components/Map/hooks/useStoredInviteCode.ts
Normal file
17
lib/src/Components/Map/hooks/useStoredInviteCode.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { useAuth } from '#components/Auth/useAuth'
|
||||
|
||||
export const useStoredInviteCode = () => {
|
||||
const inviteCode = localStorage.getItem('inviteCode')
|
||||
|
||||
const { user } = useAuth()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
if (!inviteCode || !user) return
|
||||
|
||||
navigate(`/invite/${inviteCode}`)
|
||||
}, [inviteCode, navigate, user])
|
||||
}
|
||||
@ -23,7 +23,7 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { myProfile, isMyProfileLoaded } = useMyProfile()
|
||||
const { myProfile, isMyProfileLoaded, createEmptyProfile } = useMyProfile()
|
||||
|
||||
if (!id) throw new Error('Invite ID is required')
|
||||
|
||||
@ -31,20 +31,42 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
||||
const [isRedeemingDone, setRedeemingDone] = useState(false)
|
||||
const [isValidationDone, setValidationDone] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
async function redeemInvite(id: string, myProfileId: string) {
|
||||
const invitingProfileId = await inviteApi.redeemInvite(id, myProfileId)
|
||||
async function redeemInvite(id: string, myProfileId: string) {
|
||||
const invitingProfileId = await inviteApi.redeemInvite(id, myProfileId)
|
||||
|
||||
if (invitingProfileId) {
|
||||
toast.success('Invite redeemed successfully!')
|
||||
setRedeemingDone(true)
|
||||
navigate(`/item/${invitingProfileId}`)
|
||||
} else {
|
||||
toast.error('Failed to redeem invite')
|
||||
navigate('/')
|
||||
}
|
||||
if (invitingProfileId) {
|
||||
toast.success('Invite redeemed successfully!')
|
||||
localStorage.removeItem('inviteCode')
|
||||
setRedeemingDone(true)
|
||||
navigate(`/item/${invitingProfileId}`)
|
||||
} else {
|
||||
toast.error('Failed to redeem invite')
|
||||
navigate('/')
|
||||
}
|
||||
}
|
||||
|
||||
const confirmFollowAsync = async () => {
|
||||
if (!isAuthenticated) return
|
||||
|
||||
if (!isMyProfileLoaded || isRedeemingDone) return
|
||||
|
||||
const myActualProfile = myProfile ?? (await createEmptyProfile())
|
||||
|
||||
if (!myActualProfile) {
|
||||
toast.error('Failed to create profile')
|
||||
return
|
||||
}
|
||||
|
||||
await redeemInvite(id, myActualProfile.id)
|
||||
|
||||
setRedeemingDone(true)
|
||||
}
|
||||
|
||||
const confirmFollow = () => {
|
||||
void confirmFollowAsync()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function validateInvite(id: string) {
|
||||
const invitingProfileId = await inviteApi.validateInvite(id)
|
||||
|
||||
@ -72,23 +94,14 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
||||
|
||||
if (!isAuthenticationInitialized) return
|
||||
|
||||
if (isAuthenticated) {
|
||||
if (!isMyProfileLoaded || isRedeemingDone) return
|
||||
|
||||
if (!myProfile) {
|
||||
toast.error('Could not find your profile to redeem the invite.')
|
||||
} else {
|
||||
void redeemInvite(id, myProfile.id)
|
||||
}
|
||||
setRedeemingDone(true)
|
||||
} else {
|
||||
if (isValidationDone) return
|
||||
if (isValidationDone) return
|
||||
|
||||
if (!isAuthenticated) {
|
||||
// Save invite code in local storage
|
||||
localStorage.setItem('inviteCode', id)
|
||||
|
||||
void validateInvite(id)
|
||||
}
|
||||
|
||||
void validateInvite(id)
|
||||
}, [
|
||||
id,
|
||||
isAuthenticated,
|
||||
@ -100,6 +113,7 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
||||
itemsApi,
|
||||
isRedeemingDone,
|
||||
isValidationDone,
|
||||
createEmptyProfile,
|
||||
])
|
||||
|
||||
const goToSignup = () => {
|
||||
@ -110,6 +124,35 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
||||
navigate('/login')
|
||||
}
|
||||
|
||||
const goToStart = () => {
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<MapOverlayPage backdrop className='tw:max-w-xs tw:h-fit'>
|
||||
<h2 className='tw-text-2xl tw-font-semibold tw-mb-2 tw-text-center'>Confirmation</h2>
|
||||
{invitingProfile ? (
|
||||
<div className='tw-text-center tw-mb-4'>
|
||||
<p className='tw-text-sm tw-text-gray-600'>
|
||||
Do you want to follow <strong>{invitingProfile.name}</strong>?
|
||||
</p>
|
||||
<div className='tw-flex tw:justify-center tw:mt-4'>
|
||||
<button className='tw-btn tw-btn-primary' onClick={confirmFollow}>
|
||||
Yes
|
||||
</button>
|
||||
<button className='tw-btn tw-btn-secondary' onClick={goToStart}>
|
||||
No
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className='tw-text-center'>Validating invite...</p>
|
||||
)}
|
||||
</MapOverlayPage>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<MapOverlayPage backdrop className='tw:max-w-xs tw:h-fit'>
|
||||
<h2 className='tw:text-2xl tw:font-semibold tw:mb-2 tw:text-center'>Invitation</h2>
|
||||
@ -121,17 +164,12 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
||||
community.
|
||||
</p>
|
||||
<div className='tw-flex tw:justify-center tw:mt-4'>
|
||||
<button
|
||||
className='tw-btn tw-btn-primary'
|
||||
onClick={isAuthenticated ? () => navigate('/') : goToSignup}
|
||||
>
|
||||
{isAuthenticated ? 'Go to Dashboard' : 'Sign Up'}
|
||||
<button className='tw-btn tw-btn-primary' onClick={goToSignup}>
|
||||
{'Sign Up'}
|
||||
</button>
|
||||
<button className='tw-btn tw-btn-secondary' onClick={goToLogin}>
|
||||
Login
|
||||
</button>
|
||||
{!isAuthenticated && (
|
||||
<button className='tw-btn tw-btn-secondary' onClick={goToLogin}>
|
||||
Login
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user