refactored profile creation

This commit is contained in:
Anton Tranelis 2025-12-18 11:39:41 +01:00
parent f8979ed508
commit 688c6d866b
7 changed files with 110 additions and 168 deletions

View File

@ -9,8 +9,7 @@ import { toast } from 'react-toastify'
import TargetSVG from '#assets/target.svg'
import { useAuth } from '#components/Auth/useAuth'
import { useAddItem, useUpdateItem } from '#components/Map/hooks/useItems'
import { useLayers } from '#components/Map/hooks/useLayers'
import { useUpdateItem } from '#components/Map/hooks/useItems'
import { useMyProfile } from '#components/Map/hooks/useMyProfile'
import DialogModal from '#components/Templates/DialogModal'
@ -37,8 +36,6 @@ export const LocateControl = (): React.JSX.Element => {
const map = useMap()
const myProfile = useMyProfile()
const updateItem = useUpdateItem()
const addItem = useAddItem()
const layers = useLayers()
const { user } = useAuth()
const navigate = useNavigate()
@ -138,61 +135,32 @@ export const LocateControl = (): React.JSX.Element => {
const itemUpdatePosition = useCallback(async () => {
if (!foundLocation || !user) return
const toastId = toast.loading(
myProfile.myProfile ? 'Updating position' : 'Creating profile at location',
)
if (!myProfile.myProfile) {
toast.error('Profile not found. Please wait for your profile to be created.')
return
}
const toastId = toast.loading('Updating position')
try {
let result: Item
if (myProfile.myProfile) {
// Update existing profile
const updatedProfile = {
id: myProfile.myProfile.id,
position: { type: 'Point', coordinates: [foundLocation.lng, foundLocation.lat] },
}
if (!myProfile.myProfile.layer?.api?.updateItem) {
throw new Error('Update API not available')
}
result = await myProfile.myProfile.layer.api.updateItem(updatedProfile as Item)
// Use server response for local state update
updateItem({ ...result, layer: myProfile.myProfile.layer, user_created: user })
toast.update(toastId, {
render: 'Position updated',
type: 'success',
isLoading: false,
autoClose: 5000,
closeButton: true,
})
} else {
// Create new profile
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',
position: { type: 'Point', coordinates: [foundLocation.lng, foundLocation.lat] },
}
result = await userLayer.api.createItem(newProfile as Item)
// Use server response for local state update
addItem({
...result,
user_created: user,
layer: userLayer,
public_edit: false,
})
toast.update(toastId, {
render: 'Profile created at location',
type: 'success',
isLoading: false,
autoClose: 5000,
closeButton: true,
})
// Update existing profile position
const updatedProfile = {
id: myProfile.myProfile.id,
position: { type: 'Point', coordinates: [foundLocation.lng, foundLocation.lat] },
}
if (!myProfile.myProfile.layer?.api?.updateItem) {
throw new Error('Update API not available')
}
const result = await myProfile.myProfile.layer.api.updateItem(updatedProfile as Item)
// Use server response for local state update
updateItem({ ...result, layer: myProfile.myProfile.layer, user_created: user })
toast.update(toastId, {
render: 'Position updated',
type: 'success',
isLoading: false,
autoClose: 5000,
closeButton: true,
})
// Navigate to the profile to show the popup
navigate(`/${result.id}`)
@ -232,7 +200,7 @@ export const LocateControl = (): React.JSX.Element => {
throw error
}
}
}, [myProfile.myProfile, foundLocation, updateItem, addItem, layers, user, lc, navigate])
}, [myProfile.myProfile, foundLocation, updateItem, user, lc, navigate])
return (
<>
@ -273,11 +241,7 @@ export const LocateControl = (): React.JSX.Element => {
className='tw:bottom-1/3 tw:mx-4 tw:sm:mx-auto'
>
<div className='tw:text-center'>
<p className='tw:mb-4'>
{myProfile.myProfile
? 'Do you like to place your profile at your current location?'
: 'Do you like to create your profile at your current location?'}
</p>
<p className='tw:mb-4'>Do you want to update your profile location?</p>
<div className='tw:flex tw:justify-between'>
<label
className='tw:btn tw:mt-4 tw:btn-primary'

View File

@ -143,47 +143,58 @@ export function ItemFormPopup(props: Props) {
async (formItem: Item) => {
if (!popupForm) return false
const existingUserItem = items.find(
(i) => i.user_created?.id === user?.id && i.layer === popupForm.layer,
)
// For userProfileLayer, profile must already exist (auto-created on login)
if (popupForm.layer.userProfileLayer) {
const existingUserItem = items.find(
(i) => i.user_created?.id === user?.id && i.layer === popupForm.layer,
)
if (!existingUserItem) {
toast.error('Profile not found. Please wait for your profile to be created.')
return false
}
// Update existing profile
const result = await handleApiOperation(
() =>
popupForm.layer.api?.updateItem!({ ...formItem, id: existingUserItem.id }) ??
Promise.resolve({} as Item),
'Profile updated',
)
if (result.success && result.data) {
const itemWithLayer = {
...result.data,
layer: popupForm.layer,
user_created: user ?? undefined,
}
updateItem(itemWithLayer)
resetFilterTags()
}
return result.success
}
// For non-userProfileLayer, create new item
const itemName = formItem.name ?? user?.first_name
if (!itemName) {
toast.error('Name must be defined')
return false
}
const isUserProfileUpdate = popupForm.layer.userProfileLayer && existingUserItem
const operation = isUserProfileUpdate
? () =>
popupForm.layer.api?.updateItem!({ ...formItem, id: existingUserItem.id }) ??
Promise.resolve({} as Item)
: () =>
popupForm.layer.api?.createItem!({
...formItem,
name: itemName,
id: crypto.randomUUID(),
}) ?? Promise.resolve({} as Item)
const result = await handleApiOperation(
operation,
isUserProfileUpdate ? 'Profile updated' : 'New item created',
() =>
popupForm.layer.api?.createItem!({
...formItem,
name: itemName,
id: crypto.randomUUID(),
}) ?? Promise.resolve({} as Item),
'New item created',
)
if (result.success && result.data) {
// Ensure the item has the layer object attached
const itemWithLayer = {
...result.data,
layer: popupForm.layer,
user_created: user ?? undefined,
}
if (isUserProfileUpdate) {
updateItem(itemWithLayer)
} else {
addItem(itemWithLayer)
}
addItem(itemWithLayer)
resetFilterTags()
}

View File

@ -15,6 +15,7 @@ export const useMyProfile = () => {
const addItem = useAddItem()
const updateItem = useUpdateItem()
const isReloadingSecretRef = useRef(false)
const isCreatingProfileRef = useRef(false)
// Find the user's profile item
const myProfile = items.find(
@ -39,6 +40,9 @@ export const useMyProfile = () => {
updateItem({
...baseItem,
...reloaded,
// Preserve the full layer object (API returns only layer ID)
layer: baseItem.layer,
user_created: baseItem.user_created, // eslint-disable-line camelcase
})
break
}
@ -57,7 +61,7 @@ export const useMyProfile = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [myProfile?.id, hasSecrets])
const createEmptyProfile = async () => {
const createEmptyProfile = async (): Promise<Item | undefined> => {
if (!user) return
const userLayer = layers.find((l) => l.userProfileLayer === true)
@ -90,5 +94,16 @@ export const useMyProfile = () => {
return newItem
}
// Auto-create profile when user is logged in but has no profile
useEffect(() => {
if (user && isUserProfileLayerLoaded && !myProfile && !isCreatingProfileRef.current) {
isCreatingProfileRef.current = true
void createEmptyProfile().finally(() => {
isCreatingProfileRef.current = false
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user, isUserProfileLayerLoaded, myProfile])
return { myProfile, isMyProfileLoaded, isUserProfileLayerLoaded, createEmptyProfile }
}

View File

@ -27,7 +27,7 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
const updateItem = useUpdateItem()
const { appName } = useAppState()
const { myProfile, isUserProfileLayerLoaded, createEmptyProfile } = useMyProfile()
const { myProfile, isUserProfileLayerLoaded } = useMyProfile()
if (!id) throw new Error('Invite ID is required')
@ -50,14 +50,10 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
}
const confirmFollowAsync = async () => {
if (!isAuthenticated) return
if (!isAuthenticated || !isUserProfileLayerLoaded || isRedeemingDone) return
if (!isUserProfileLayerLoaded || isRedeemingDone) return
const myActualProfile = myProfile ?? (await createEmptyProfile())
if (!myActualProfile) {
toast.error('Failed to create profile')
if (!myProfile) {
toast.error('Profile not found. Please wait for your profile to be created.')
return
}
@ -66,13 +62,13 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
return
}
await redeemInvite(id, myActualProfile.id)
await redeemInvite(id, myProfile.id)
// Add new relation to local state
updateItem({
...myActualProfile,
...myProfile,
relations: [
...(myActualProfile.relations ?? []),
...(myProfile.relations ?? []),
{
type: 'is_following',
direction: 'outgoing',
@ -156,7 +152,6 @@ export function InvitePage({ inviteApi, itemsApi }: Props) {
itemsApi,
isRedeemingDone,
isValidationDone,
createEmptyProfile,
isUserProfileLayerLoaded,
])

View File

@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'
import { useLocation, useNavigate } from 'react-router-dom'
import { useAuth } from '#components/Auth/useAuth'
import { useItems, useUpdateItem, useAddItem } from '#components/Map/hooks/useItems'
import { useItems, useUpdateItem } from '#components/Map/hooks/useItems'
import { useLayers } from '#components/Map/hooks/useLayers'
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
@ -55,9 +55,8 @@ export function ProfileForm() {
const [updatePermission, setUpdatePermission] = useState<boolean>(false)
const [loading, setLoading] = useState<boolean>(false)
const [item, setItem] = useState<Item>()
const { user } = useAuth()
useAuth()
const updateItem = useUpdateItem()
const addItem = useAddItem()
const layers = useLayers()
const location = useLocation()
const tags = useTags()
@ -75,20 +74,9 @@ export function ProfileForm() {
useEffect(() => {
const itemId = location.pathname.split('/')[2]
if (itemId === 'new') {
const layer = layers.find((l) => l.userProfileLayer)
setItem({
id: crypto.randomUUID(),
name: user?.first_name ?? '',
text: '',
layer,
new: true,
})
} else {
const item = items.find((i) => i.id === itemId)
if (item) setItem(item)
}
}, [items, location.pathname, layers, user?.first_name])
const item = items.find((i) => i.id === itemId)
if (item) setItem(item)
}, [items, location.pathname])
useEffect(() => {
if (!item) return
@ -174,8 +162,6 @@ export function ProfileForm() {
setLoading,
navigate,
updateItem,
addItem,
user,
urlParams,
)
}}

View File

@ -206,8 +206,6 @@ export const onUpdateItem = async (
setLoading,
navigate,
updateItem,
addItem,
user,
params,
) => {
let changedItem = {} as Item
@ -292,54 +290,28 @@ export const onUpdateItem = async (
// take care that addTag request comes before item request
await sleep(200)
if (!item.new) {
const toastId = toast.loading('updating Item ...')
const toastId = toast.loading('updating Item ...')
const result = await handleApiOperation(
async () => {
const serverResult = await item?.layer?.api?.updateItem!(changedItem)
return serverResult!
},
toastId,
'Item updated',
)
const result = await handleApiOperation(
async () => {
const serverResult = await item?.layer?.api?.updateItem!(changedItem)
return serverResult!
},
toastId,
'Item updated',
)
if (result.success && result.data) {
// Use server response with additional client-side data
const itemWithLayer = {
...result.data,
layer: item.layer,
markerIcon: state.marker_icon,
gallery: state.gallery,
user_created: item.user_created,
}
updateItem(itemWithLayer)
navigate(`/item/${item.id}${params && '?' + params}`)
if (result.success && result.data) {
// Use server response with additional client-side data
const itemWithLayer = {
...result.data,
layer: item.layer,
markerIcon: state.marker_icon,
gallery: state.gallery,
user_created: item.user_created,
}
setLoading(false)
} else {
item.new = false
const toastId = toast.loading('updating Item ...')
const result = await handleApiOperation(
async () => {
const serverResult = await item.layer?.api?.createItem!(changedItem)
return serverResult!
},
toastId,
'Item updated',
)
if (result.success && result.data) {
// Use server response with additional client-side data
const itemWithLayer = {
...result.data,
layer: item.layer,
user_created: user,
}
addItem(itemWithLayer)
navigate(`/${params && '?' + params}`)
}
setLoading(false)
updateItem(itemWithLayer)
navigate(`/item/${item.id}${params && '?' + params}`)
}
setLoading(false)
}

View File

@ -56,7 +56,6 @@ export interface Item {
color?: string
markerIcon?: MarkerIcon
avatar?: string
new?: boolean
contact?: string
telephone?: string
next_appointment?: string