mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2026-01-16 09:54:40 +00:00
refactored profile creation
This commit is contained in:
parent
f8979ed508
commit
688c6d866b
@ -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'
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
|
||||
@ -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,
|
||||
])
|
||||
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
1
lib/src/types/Item.d.ts
vendored
1
lib/src/types/Item.d.ts
vendored
@ -56,7 +56,6 @@ export interface Item {
|
||||
color?: string
|
||||
markerIcon?: MarkerIcon
|
||||
avatar?: string
|
||||
new?: boolean
|
||||
contact?: string
|
||||
telephone?: string
|
||||
next_appointment?: string
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user