From 68fb2019075ce33232f27b09068b3e3de2631aeb Mon Sep 17 00:00:00 2001
From: Maximilian Harz
Date: Thu, 4 Dec 2025 00:13:33 +0100
Subject: [PATCH] Show dialog to accept following
---
lib/src/Components/Map/UtopiaMap.tsx | 6 +-
lib/src/Components/Map/hooks/useMyProfile.ts | 33 +++++-
.../Components/Map/hooks/useRedeemInvite.ts | 35 ------
.../Map/hooks/useStoredInviteCode.ts | 17 +++
lib/src/Components/Onboarding/InvitePage.tsx | 108 ++++++++++++------
5 files changed, 124 insertions(+), 75 deletions(-)
delete mode 100644 lib/src/Components/Map/hooks/useRedeemInvite.ts
create mode 100644 lib/src/Components/Map/hooks/useStoredInviteCode.ts
diff --git a/lib/src/Components/Map/UtopiaMap.tsx b/lib/src/Components/Map/UtopiaMap.tsx
index 93f933ca..dee84b1f 100644
--- a/lib/src/Components/Map/UtopiaMap.tsx
+++ b/lib/src/Components/Map/UtopiaMap.tsx
@@ -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 (
{
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 }
}
diff --git a/lib/src/Components/Map/hooks/useRedeemInvite.ts b/lib/src/Components/Map/hooks/useRedeemInvite.ts
deleted file mode 100644
index 7a403848..00000000
--- a/lib/src/Components/Map/hooks/useRedeemInvite.ts
+++ /dev/null
@@ -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])
-}
diff --git a/lib/src/Components/Map/hooks/useStoredInviteCode.ts b/lib/src/Components/Map/hooks/useStoredInviteCode.ts
new file mode 100644
index 00000000..fcd9b6f8
--- /dev/null
+++ b/lib/src/Components/Map/hooks/useStoredInviteCode.ts
@@ -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])
+}
diff --git a/lib/src/Components/Onboarding/InvitePage.tsx b/lib/src/Components/Onboarding/InvitePage.tsx
index 0e9d04a5..0f67242a 100644
--- a/lib/src/Components/Onboarding/InvitePage.tsx
+++ b/lib/src/Components/Onboarding/InvitePage.tsx
@@ -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 (
+
+ Confirmation
+ {invitingProfile ? (
+
+
+ Do you want to follow {invitingProfile.name}?
+
+
+
+
+
+
+ ) : (
+ Validating invite...
+ )}
+
+ )
+ }
+
return (
Invitation
@@ -121,17 +164,12 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
community.
-
) : (