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 { ContextWrapper } from '#components/AppShell/ContextWrapper'
|
||||||
|
|
||||||
import { useRedeemInvite } from './hooks/useRedeemInvite'
|
import { useStoredInviteCode } from './hooks/useStoredInviteCode'
|
||||||
import { UtopiaMapInner } from './UtopiaMapInner'
|
import { UtopiaMapInner } from './UtopiaMapInner'
|
||||||
|
|
||||||
import type { InviteApi } from '#types/InviteApi'
|
import type { InviteApi } from '#types/InviteApi'
|
||||||
@ -112,8 +112,8 @@ function UtopiaMap({
|
|||||||
/** tile size (default 256) */
|
/** tile size (default 256) */
|
||||||
tileSize?: number
|
tileSize?: number
|
||||||
}) {
|
}) {
|
||||||
// Check for invite code in localStorage and loaded profile, redeem if possible
|
// Check for invite code in localStorage
|
||||||
useRedeemInvite(inviteApi)
|
useStoredInviteCode()
|
||||||
return (
|
return (
|
||||||
<ContextWrapper>
|
<ContextWrapper>
|
||||||
<MapContainer
|
<MapContainer
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import { useAuth } from '#components/Auth/useAuth'
|
import { useAuth } from '#components/Auth/useAuth'
|
||||||
|
|
||||||
import { useItems } from './useItems'
|
import { useItems, useAddItem } from './useItems'
|
||||||
|
import { useLayers } from './useLayers'
|
||||||
|
|
||||||
export const useMyProfile = () => {
|
export const useMyProfile = () => {
|
||||||
const items = useItems()
|
const items = useItems()
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
|
const layers = useLayers()
|
||||||
|
const addItem = useAddItem()
|
||||||
|
|
||||||
// Find the user's profile item
|
// Find the user's profile item
|
||||||
const myProfile = items.find(
|
const myProfile = items.find(
|
||||||
@ -16,5 +19,31 @@ export const useMyProfile = () => {
|
|||||||
// allItemsLoaded is not reliable
|
// allItemsLoaded is not reliable
|
||||||
const isMyProfileLoaded = isAnyUserProfileLoaded && !!user
|
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 { id } = useParams<{ id: string }>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { myProfile, isMyProfileLoaded } = useMyProfile()
|
const { myProfile, isMyProfileLoaded, createEmptyProfile } = useMyProfile()
|
||||||
|
|
||||||
if (!id) throw new Error('Invite ID is required')
|
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 [isRedeemingDone, setRedeemingDone] = useState(false)
|
||||||
const [isValidationDone, setValidationDone] = useState(false)
|
const [isValidationDone, setValidationDone] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
async function redeemInvite(id: string, myProfileId: string) {
|
||||||
async function redeemInvite(id: string, myProfileId: string) {
|
const invitingProfileId = await inviteApi.redeemInvite(id, myProfileId)
|
||||||
const invitingProfileId = await inviteApi.redeemInvite(id, myProfileId)
|
|
||||||
|
|
||||||
if (invitingProfileId) {
|
if (invitingProfileId) {
|
||||||
toast.success('Invite redeemed successfully!')
|
toast.success('Invite redeemed successfully!')
|
||||||
setRedeemingDone(true)
|
localStorage.removeItem('inviteCode')
|
||||||
navigate(`/item/${invitingProfileId}`)
|
setRedeemingDone(true)
|
||||||
} else {
|
navigate(`/item/${invitingProfileId}`)
|
||||||
toast.error('Failed to redeem invite')
|
} else {
|
||||||
navigate('/')
|
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) {
|
async function validateInvite(id: string) {
|
||||||
const invitingProfileId = await inviteApi.validateInvite(id)
|
const invitingProfileId = await inviteApi.validateInvite(id)
|
||||||
|
|
||||||
@ -72,23 +94,14 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
|||||||
|
|
||||||
if (!isAuthenticationInitialized) return
|
if (!isAuthenticationInitialized) return
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isValidationDone) return
|
||||||
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 (!isAuthenticated) {
|
||||||
// Save invite code in local storage
|
// Save invite code in local storage
|
||||||
localStorage.setItem('inviteCode', id)
|
localStorage.setItem('inviteCode', id)
|
||||||
|
|
||||||
void validateInvite(id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void validateInvite(id)
|
||||||
}, [
|
}, [
|
||||||
id,
|
id,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
@ -100,6 +113,7 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
|||||||
itemsApi,
|
itemsApi,
|
||||||
isRedeemingDone,
|
isRedeemingDone,
|
||||||
isValidationDone,
|
isValidationDone,
|
||||||
|
createEmptyProfile,
|
||||||
])
|
])
|
||||||
|
|
||||||
const goToSignup = () => {
|
const goToSignup = () => {
|
||||||
@ -110,6 +124,35 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
|
|||||||
navigate('/login')
|
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 (
|
return (
|
||||||
<MapOverlayPage backdrop className='tw:max-w-xs tw:h-fit'>
|
<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>
|
<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.
|
community.
|
||||||
</p>
|
</p>
|
||||||
<div className='tw-flex tw:justify-center tw:mt-4'>
|
<div className='tw-flex tw:justify-center tw:mt-4'>
|
||||||
<button
|
<button className='tw-btn tw-btn-primary' onClick={goToSignup}>
|
||||||
className='tw-btn tw-btn-primary'
|
{'Sign Up'}
|
||||||
onClick={isAuthenticated ? () => navigate('/') : goToSignup}
|
</button>
|
||||||
>
|
<button className='tw-btn tw-btn-secondary' onClick={goToLogin}>
|
||||||
{isAuthenticated ? 'Go to Dashboard' : 'Sign Up'}
|
Login
|
||||||
</button>
|
</button>
|
||||||
{!isAuthenticated && (
|
|
||||||
<button className='tw-btn tw-btn-secondary' onClick={goToLogin}>
|
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user