diff --git a/package-lock.json b/package-lock.json index 4d8eea8e..3b086789 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "utopia-ui", - "version": "3.0.10", + "version": "3.0.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "utopia-ui", - "version": "3.0.10", - "license": "MIT", + "version": "3.0.19", + "license": "GPL-3.0-only", "dependencies": { "@heroicons/react": "^2.0.17", "@tanstack/react-query": "^5.17.8", diff --git a/package.json b/package.json index 35dc9964..fc493e10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "utopia-ui", - "version": "3.0.10", + "version": "3.0.19", "description": "Reuseable React Components to build mapping apps for real life communities and networks", "repository": "https://github.com/utopia-os/utopia-ui", "homepage:": "https://utopia-os.org/", @@ -16,7 +16,7 @@ ], "keywords": [], "author": "Anton Tranelis", - "license": "MIT", + "license": "GPL-3.0-only", "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.4.1", "@types/leaflet": "^1.7.11", diff --git a/src/Components/AppShell/AppShell.tsx b/src/Components/AppShell/AppShell.tsx index 5827a2b8..3d5acebc 100644 --- a/src/Components/AppShell/AppShell.tsx +++ b/src/Components/AppShell/AppShell.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import NavBar from './NavBar' -import { SetAssetsApi } from './SetAssetsApi' +import { SetAppState } from './SetAppState' import { AssetsApi } from '../../types' import { ContextWrapper } from './ContextWrapper' @@ -18,7 +18,7 @@ export function AppShell({ return (
- +
{children} diff --git a/src/Components/AppShell/ContextWrapper.tsx b/src/Components/AppShell/ContextWrapper.tsx index 85169c91..69bd6a1a 100644 --- a/src/Components/AppShell/ContextWrapper.tsx +++ b/src/Components/AppShell/ContextWrapper.tsx @@ -10,7 +10,7 @@ import { LeafletRefsProvider } from '../Map/hooks/useLeafletRefs' import { PermissionsProvider } from '../Map/hooks/usePermissions' import { SelectPositionProvider } from '../Map/hooks/useSelectPosition' import { TagsProvider } from '../Map/hooks/useTags' -import { AssetsProvider } from './hooks/useAssets' +import { AppStateProvider } from './hooks/useAppState' import { useContext, createContext } from 'react' import { BrowserRouter as Router, useLocation } from 'react-router-dom' @@ -72,7 +72,7 @@ export const Wrappers = ({ children }) => { - + { {children} - + diff --git a/src/Components/AppShell/SetAppState.tsx b/src/Components/AppShell/SetAppState.tsx new file mode 100644 index 00000000..fb0275de --- /dev/null +++ b/src/Components/AppShell/SetAppState.tsx @@ -0,0 +1,24 @@ +import { useSetAppState } from './hooks/useAppState' +import { AssetsApi } from '../../types' +import { useEffect } from 'react' + +export const SetAppState = ({ + assetsApi, + userType, +}: { + assetsApi: AssetsApi + userType: string +}) => { + const setAppState = useSetAppState() + + useEffect(() => { + setAppState({ assetsApi }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [assetsApi]) + + useEffect(() => { + setAppState({ userType }) + }, [setAppState, userType]) + + return <> +} diff --git a/src/Components/AppShell/SetAssetsApi.tsx b/src/Components/AppShell/SetAssetsApi.tsx deleted file mode 100644 index d1a69d3d..00000000 --- a/src/Components/AppShell/SetAssetsApi.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useSetAssetApi } from './hooks/useAssets' -import { AssetsApi } from '../../types' -import { useEffect } from 'react' - -export const SetAssetsApi = ({ assetsApi }: { assetsApi: AssetsApi }) => { - const setAssetsApi = useSetAssetApi() - - useEffect(() => { - setAssetsApi(assetsApi) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [assetsApi]) - - return <> -} diff --git a/src/Components/AppShell/hooks/useAppState.tsx b/src/Components/AppShell/hooks/useAppState.tsx new file mode 100644 index 00000000..0124fb9d --- /dev/null +++ b/src/Components/AppShell/hooks/useAppState.tsx @@ -0,0 +1,50 @@ +import { useCallback, useState, createContext, useContext } from 'react' +import * as React from 'react' +import { AssetsApi } from '../../../types' + +type AppState = { + assetsApi: AssetsApi + userType: string +} + +type UseAppManagerResult = ReturnType + +const initialAppState: AppState = { + assetsApi: {} as AssetsApi, + userType: '', +} + +const AppContext = createContext({ + state: initialAppState, + setAppState: () => {}, +}) + +function useAppManager(): { + state: AppState + setAppState: (newState: Partial) => void +} { + const [state, setState] = useState(initialAppState) + + const setAppState = useCallback((newState: Partial) => { + setState((prevState) => ({ + ...prevState, + ...newState, + })) + }, []) + + return { state, setAppState } +} + +export const AppStateProvider: React.FunctionComponent<{ + children?: React.ReactNode +}> = ({ children }) => {children} + +export const useAppState = (): AppState => { + const { state } = useContext(AppContext) + return state +} + +export const useSetAppState = (): UseAppManagerResult['setAppState'] => { + const { setAppState } = useContext(AppContext) + return setAppState +} diff --git a/src/Components/Input/ComboBoxInput.tsx b/src/Components/Input/ComboBoxInput.tsx index 8d656617..3491102d 100644 --- a/src/Components/Input/ComboBoxInput.tsx +++ b/src/Components/Input/ComboBoxInput.tsx @@ -1,20 +1,15 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { useState } from 'react' import * as React from 'react' interface ComboBoxProps { id?: string - options: { value: string; label: string }[] + options: string[] value: string onValueChange: (newValue: string) => void } const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => { - const [selectedValue, setSelectedValue] = useState(value) - const handleChange = (e: React.ChangeEvent) => { const value = e.target.value - setSelectedValue(value) onValueChange(value) } @@ -25,8 +20,8 @@ const ComboBoxInput = ({ id, options, value, onValueChange }: ComboBoxProps) => onChange={handleChange} > {options.map((o) => ( - ))} diff --git a/src/Components/Map/Subcomponents/ItemFormPopup.tsx b/src/Components/Map/Subcomponents/ItemFormPopup.tsx index 76960648..65bf111e 100644 --- a/src/Components/Map/Subcomponents/ItemFormPopup.tsx +++ b/src/Components/Map/Subcomponents/ItemFormPopup.tsx @@ -113,8 +113,8 @@ export function ItemFormPopup(props: ItemFormPopupProps) { toast.error(error.toString()) } if (success) { - props.layer.onlyOnePerOwner && item && updateItem({ ...item, ...formItem }) - ;(!props.layer.onlyOnePerOwner || !item) && + if (props.layer.onlyOnePerOwner && item) updateItem({ ...item, ...formItem }) + if (!props.layer.onlyOnePerOwner || !item) { addItem({ ...formItem, name: formItem.name ? formItem.name : user?.first_name, @@ -124,6 +124,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) { layer: props.layer, public_edit: !user, }) + } toast.success('New item created') resetFilterTags() } diff --git a/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx b/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx index 1a3729e7..4257e144 100644 --- a/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx +++ b/src/Components/Map/Subcomponents/ItemPopupComponents/HeaderView.tsx @@ -13,7 +13,7 @@ import * as React from 'react' import { Item, ItemsApi } from '../../../../types' import { useHasUserPermission } from '../../hooks/usePermissions' import { getValue } from '../../../../Utils/GetValue' -import { useAssetApi } from '../../../AppShell/hooks/useAssets' +import { useAppState } from '../../../AppShell/hooks/useAppState' import DialogModal from '../../../Templates/DialogModal' import { useNavigate } from 'react-router-dom' @@ -52,18 +52,18 @@ export function HeaderView({ const hasUserPermission = useHasUserPermission() const navigate = useNavigate() - const assetsApi = useAssetApi() + const appState = useAppState() const avatar = itemAvatarField && getValue(item, itemAvatarField) - ? assetsApi.url + + ? appState.assetsApi.url + getValue(item, itemAvatarField) + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}` : item.layer?.itemAvatarField && item && - getValue(item, item.layer.itemAvatarField) && - assetsApi.url + - getValue(item, item.layer.itemAvatarField) + + getValue(item, item.layer?.itemAvatarField) && + appState.assetsApi.url + + getValue(item, item.layer?.itemAvatarField) + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}` const title = itemNameField ? getValue(item, itemNameField) diff --git a/src/Components/Map/Subcomponents/ItemPopupComponents/PopupStartEndInput.tsx b/src/Components/Map/Subcomponents/ItemPopupComponents/PopupStartEndInput.tsx index 70d9f21c..2c11baf3 100644 --- a/src/Components/Map/Subcomponents/ItemPopupComponents/PopupStartEndInput.tsx +++ b/src/Components/Map/Subcomponents/ItemPopupComponents/PopupStartEndInput.tsx @@ -17,7 +17,7 @@ export const PopupStartEndInput = ({ updateEndValue, }: StartEndInputProps) => { return ( -
+
) } diff --git a/src/Components/Map/UtopiaMap.css b/src/Components/Map/UtopiaMap.css index 389e3469..5ab6dc05 100644 --- a/src/Components/Map/UtopiaMap.css +++ b/src/Components/Map/UtopiaMap.css @@ -120,6 +120,20 @@ left: 4px; width: 24px; } + + .flower-icon { + position: relative; + top: -35px; + left: 4px; + width: 24px; + } + + .network-icon { + position: relative; + top: -35px; + left: 4px; + width: 24px; + } .leaflet-popup-scrolled { overflow-x: hidden; diff --git a/src/Components/Profile/ProfileForm.tsx b/src/Components/Profile/ProfileForm.tsx index d93b2afd..c5ece9ca 100644 --- a/src/Components/Profile/ProfileForm.tsx +++ b/src/Components/Profile/ProfileForm.tsx @@ -5,7 +5,7 @@ /* eslint-disable @typescript-eslint/no-unnecessary-condition */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable no-constant-condition */ + import { useItems, useUpdateItem, useAddItem } from '../Map/hooks/useItems' import { useEffect, useState } from 'react' import { getValue } from '../../Utils/GetValue' @@ -21,21 +21,23 @@ import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions' import { SimpleForm } from './Templates/SimpleForm' import { TabsForm } from './Templates/TabsForm' import { FormHeader } from './Subcomponents/FormHeader' +import { useAppState } from '../AppShell/hooks/useAppState' +import { FlexForm } from './Templates/FlexForm' -export function ProfileForm({ userType }: { userType: string }) { +export function ProfileForm() { const [state, setState] = useState({ color: '', id: '', - groupType: 'wuerdekompass', + group_type: 'wuerdekompass', status: 'active', name: '', subname: '', text: '', contact: '', telephone: '', - nextAppointment: '', + next_appointment: '', image: '', - markerIcon: '', + marker_icon: '', offers: [] as Tag[], needs: [] as Tag[], relations: [] as Item[], @@ -57,13 +59,13 @@ export function ProfileForm({ userType }: { userType: string }) { const hasUserPermission = useHasUserPermission() const getItemTags = useGetItemTags() const items = useItems() + const appState = useAppState() const [urlParams, setUrlParams] = useState(new URLSearchParams(location.search)) useEffect(() => { item && hasUserPermission('items', 'update', item) && setUpdatePermission(true) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [item]) + }, [hasUserPermission, item]) useEffect(() => { const itemId = location.pathname.split('/')[2] @@ -71,9 +73,8 @@ export function ProfileForm({ userType }: { userType: string }) { const item = items.find((i) => i.id === itemId) item && setItem(item) - const layer = layers.find((l) => l.itemType.name === userType) - - !item && + if (!item) { + const layer = layers.find((l) => l.itemType.name === appState.userType) setItem({ id: crypto.randomUUID(), name: user ? user.first_name : '', @@ -81,6 +82,7 @@ export function ProfileForm({ userType }: { userType: string }) { layer, new: true, }) + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [items]) @@ -113,17 +115,17 @@ export function ProfileForm({ userType }: { userType: string }) { setState({ color: newColor, - id: item.id ?? '', - groupType: item.group_type ?? 'wuerdekompass', - status: item.status ?? 'active', - name: item.name ?? '', - subname: item.subname ?? '', - text: item.text ?? '', - contact: item.contact ?? '', - telephone: item.telephone ?? '', - nextAppointment: item.next_appointment ?? '', - image: item.image ?? '', - markerIcon: item.marker_icon ?? '', + id: item?.id ?? '', + group_type: item?.group_type ?? '', + status: item?.status ?? '', + name: item?.name ?? '', + subname: item?.subname ?? '', + text: item?.text ?? '', + contact: item?.contact ?? '', + telephone: item?.telephone ?? '', + next_appointment: item?.next_appointment ?? '', + image: item?.image ?? '', + marker_icon: item?.marker_icon ?? '', offers, needs, relations, @@ -136,8 +138,8 @@ export function ProfileForm({ userType }: { userType: string }) { const [template, setTemplate] = useState('') useEffect(() => { - setTemplate(item.layer?.itemType.template || userType) - }, [userType, item]) + setTemplate(item.layer?.itemType.template || appState.userType) + }, [appState.userType, item]) return ( <> @@ -154,6 +156,10 @@ export function ProfileForm({ userType }: { userType: string }) { {template === 'simple' && } + {template === 'flex' && ( + + )} + {template === 'tabs' && ( )} -
+
diff --git a/src/Components/Profile/ProfileView.tsx b/src/Components/Profile/ProfileView.tsx index 6656b52a..03fd13bf 100644 --- a/src/Components/Profile/ProfileView.tsx +++ b/src/Components/Profile/ProfileView.tsx @@ -25,14 +25,10 @@ import { OnepagerView } from './Templates/OnepagerView' import { SimpleView } from './Templates/SimpleView' import { handleDelete, linkItem, unlinkItem } from './itemFunctions' import { useTags } from '../Map/hooks/useTags' +import { FlexView } from './Templates/FlexView' +import { useAppState } from '../AppShell/hooks/useAppState' -export function ProfileView({ - userType, - attestationApi, -}: { - userType: string - attestationApi?: ItemsApi -}) { +export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi }) { const [item, setItem] = useState() const [updatePermission, setUpdatePermission] = useState(false) const [relations, setRelations] = useState([]) @@ -53,6 +49,7 @@ export function ProfileView({ const setSelectPosition = useSetSelectPosition() const clusterRef = useClusterRef() const leafletRefs = useLeafletRefs() + const appState = useAppState() const [attestations, setAttestations] = useState([]) @@ -158,8 +155,8 @@ export function ProfileView({ }, [selectPosition]) useEffect(() => { - setTemplate(item?.layer?.itemType.template || userType) - }, [userType, item]) + setTemplate(item?.layer?.itemType.template || appState.userType) + }, [appState.userType, item]) return ( <> @@ -185,13 +182,14 @@ export function ProfileView({ />
- {template === 'onepager' && } + {template === 'onepager' && } {template === 'simple' && } + {template === 'flex' && } + {template === 'tabs' && ( = ({ avatar, setAvatar }) const [cropModalOpen, setCropModalOpen] = useState(false) const [cropping, setCropping] = useState(false) - const assetsApi = useAssetApi() + const appState = useAppState() const imgRef = useRef(null) @@ -151,10 +151,10 @@ export const AvatarWidget: React.FC = ({ avatar, setAvatar }) ctx?.drawImage(img, 0, 0, 400, 400) const resizedBlob = await canvas.convertToBlob() - const asset = await assetsApi.upload(resizedBlob, 'avatar') + const asset = await appState.assetsApi.upload(resizedBlob, 'avatar') setAvatar(asset.id) }, - [assetsApi, setAvatar], + [appState.assetsApi, setAvatar], ) return ( @@ -185,7 +185,10 @@ export const AvatarWidget: React.FC = ({ avatar, setAvatar })
{avatar ? (
- +
) : (
diff --git a/src/Components/Profile/Subcomponents/ContactInfoForm.tsx b/src/Components/Profile/Subcomponents/ContactInfoForm.tsx new file mode 100644 index 00000000..70e7b034 --- /dev/null +++ b/src/Components/Profile/Subcomponents/ContactInfoForm.tsx @@ -0,0 +1,54 @@ +import * as React from 'react' +import { TextInput } from '../../Input' +import { FormState } from '../Templates/OnepagerForm' + +export const ContactInfoForm = ({ + state, + setState, +}: { + state: FormState + setState: React.Dispatch> +}) => { + return ( +
+
+ + + setState((prevState) => ({ + ...prevState, + contact: v, + })) + } + /> +
+ +
+ + + setState((prevState) => ({ + ...prevState, + telephone: v, + })) + } + /> +
+
+ ) +} diff --git a/src/Components/Profile/Subcomponents/ContactInfo.tsx b/src/Components/Profile/Subcomponents/ContactInfoView.tsx similarity index 65% rename from src/Components/Profile/Subcomponents/ContactInfo.tsx rename to src/Components/Profile/Subcomponents/ContactInfoView.tsx index 714fa3a6..a8d3a4ec 100644 --- a/src/Components/Profile/Subcomponents/ContactInfo.tsx +++ b/src/Components/Profile/Subcomponents/ContactInfoView.tsx @@ -1,35 +1,48 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */ /* eslint-disable @typescript-eslint/restrict-plus-operands */ import { Link } from 'react-router-dom' -import { useAssetApi } from '../../AppShell/hooks/useAssets' +import { useAppState } from '../../AppShell/hooks/useAppState' +import { Item } from '../../../types' +import { useEffect, useState } from 'react' +import { useItems } from '../../Map/hooks/useItems' -const ContactInfo = ({ - email, - telephone, - name, - avatar, - link, -}: { - email: string - telephone: string - name: string - avatar: string - link?: string -}) => { - const assetsApi = useAssetApi() +export const ContactInfoView = ({ item, heading }: { item: Item; heading: string }) => { + const appState = useAppState() + const [profileOwner, setProfileOwner] = useState() + const items = useItems() + + useEffect(() => { + console.log( + 'user:', + items.find( + (i) => + i.user_created?.id === item.user_created?.id && + i.layer?.itemType.name === appState.userType, + ), + ) + + setProfileOwner( + items.find( + (i) => + i.user_created?.id === item.user_created?.id && + i.layer?.itemType.name === appState.userType, + ), + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [item, items]) return (
-

Du hast Fragen?

+

{heading}

- {avatar && ( - + {profileOwner?.image && ( +
{name}
@@ -38,11 +51,11 @@ const ContactInfo = ({ )}
-

{name}

- {email && ( +

{profileOwner?.name}

+ {item.contact && (

- {email} + {item.contact}

)} - {telephone && ( + {item.telephone && (

- {telephone} + {item.telephone}

)} @@ -90,8 +103,6 @@ const ContactInfo = ({ ) } -export default ContactInfo - // eslint-disable-next-line react/prop-types const ConditionalLink = ({ url, children }) => { const params = new URLSearchParams(window.location.search) diff --git a/src/Components/Profile/Subcomponents/GroupSubHeaderView.tsx b/src/Components/Profile/Subcomponents/GroupSubHeaderView.tsx new file mode 100644 index 00000000..70a0a41f --- /dev/null +++ b/src/Components/Profile/Subcomponents/GroupSubHeaderView.tsx @@ -0,0 +1,42 @@ +import { Item } from '../../../types' +import SocialShareBar from './SocialShareBar' + +export const GroupSubHeaderView = ({ + item, + shareBaseUrl, + platforms, +}: { + item: Item + shareBaseUrl: string + platforms?: string[] +}) => ( +
+
+ {item.status && ( +
+ + {item.status} + +
+ )} + {item.group_type && ( +
+ + {item.group_type} + +
+ )} +
+
+ +
+
+) diff --git a/src/Components/Profile/Subcomponents/GroupSubheaderForm.tsx b/src/Components/Profile/Subcomponents/GroupSubheaderForm.tsx new file mode 100644 index 00000000..53bf733b --- /dev/null +++ b/src/Components/Profile/Subcomponents/GroupSubheaderForm.tsx @@ -0,0 +1,87 @@ +import * as React from 'react' +import ComboBoxInput from '../../Input/ComboBoxInput' +import { Item } from '../../../types' +import { useEffect } from 'react' +import { FormState } from '../Templates/OnepagerForm' + +type groupType = { + groupTypes_id: { + name: string + color: string + image: string + markerIcon: string + } +} + +export const GroupSubheaderForm = ({ + state, + setState, + groupStates, + groupTypes, +}: { + state: FormState + setState: React.Dispatch> + item: Item + groupStates?: string[] + groupTypes?: groupType[] +}) => { + useEffect(() => { + if (groupTypes && groupStates) { + const groupType = groupTypes.find((gt) => gt.groupTypes_id.name === state.group_type) + console.log(state.group_type) + setState((prevState) => ({ + ...prevState, + color: groupType?.groupTypes_id.color || groupTypes[0].groupTypes_id.color, + marker_icon: groupType?.groupTypes_id.markerIcon || groupTypes[0].groupTypes_id.markerIcon, + image: groupType?.groupTypes_id.image || groupTypes[0].groupTypes_id.image, + status: state.status || groupStates[0], + group_type: state.group_type || groupTypes[0].groupTypes_id.name, + })) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state.group_type, groupTypes]) + + return ( +
+
+ + + setState((prevState) => ({ + ...prevState, + status: v, + })) + } + /> +
+
+ + gt.groupTypes_id.name) || []} + value={state.group_type} + onValueChange={(v) => + setState((prevState) => ({ + ...prevState, + group_type: v, + })) + } + /> +
+
+ ) +} diff --git a/src/Components/Profile/Subcomponents/LinkedItemsHeaderView.tsx b/src/Components/Profile/Subcomponents/LinkedItemsHeaderView.tsx index c2277f1a..0dafb1a1 100644 --- a/src/Components/Profile/Subcomponents/LinkedItemsHeaderView.tsx +++ b/src/Components/Profile/Subcomponents/LinkedItemsHeaderView.tsx @@ -8,7 +8,7 @@ import { useEffect } from 'react' import { getValue } from '../../../Utils/GetValue' import { Item } from '../../../types' -import { useAssetApi } from '../../AppShell/hooks/useAssets' +import { useAppState } from '../../AppShell/hooks/useAppState' export function LinkedItemsHeaderView({ item, @@ -27,15 +27,15 @@ export function LinkedItemsHeaderView({ loading?: boolean unlinkPermission: boolean }) { - const assetsApi = useAssetApi() + const appState = useAppState() const avatar = itemAvatarField && getValue(item, itemAvatarField) - ? assetsApi.url + getValue(item, itemAvatarField) + ? appState.assetsApi.url + getValue(item, itemAvatarField) : item.layer?.itemAvatarField && item && - getValue(item, item.layer.itemAvatarField) && - assetsApi.url + getValue(item, item.layer.itemAvatarField) + getValue(item, item.layer?.itemAvatarField) && + appState.assetsApi.url + getValue(item, item.layer?.itemAvatarField) const title = itemNameField ? getValue(item, itemNameField) : item.layer?.itemNameField && item && getValue(item, item.layer.itemNameField) diff --git a/src/Components/Profile/Subcomponents/ProfileStartEndForm.tsx b/src/Components/Profile/Subcomponents/ProfileStartEndForm.tsx new file mode 100644 index 00000000..feebaf24 --- /dev/null +++ b/src/Components/Profile/Subcomponents/ProfileStartEndForm.tsx @@ -0,0 +1,30 @@ +import * as React from 'react' +import { PopupStartEndInput } from '../../Map' +import { Item } from '../../../types' + +export const ProfileStartEndForm = ({ + item, + setState, +}: { + item: Item + setState: React.Dispatch> +}) => { + return ( + + setState((prevState) => ({ + ...prevState, + end: e, + })) + } + updateStartValue={(s) => + setState((prevState) => ({ + ...prevState, + start: s, + })) + } + > + ) +} diff --git a/src/Components/Profile/Subcomponents/ProfileStartEndView.tsx b/src/Components/Profile/Subcomponents/ProfileStartEndView.tsx new file mode 100644 index 00000000..06935fca --- /dev/null +++ b/src/Components/Profile/Subcomponents/ProfileStartEndView.tsx @@ -0,0 +1,11 @@ +import * as React from 'react' +import { StartEndView } from '../../Map' +import { Item } from '../../../types' + +export const ProfileStartEndView = ({ item }: { item: Item }) => { + return ( +
+ +
+ ) +} diff --git a/src/Components/Profile/Subcomponents/ProfileSubHeader.tsx b/src/Components/Profile/Subcomponents/ProfileSubHeader.tsx deleted file mode 100644 index 84e69758..00000000 --- a/src/Components/Profile/Subcomponents/ProfileSubHeader.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -import SocialShareBar from './SocialShareBar' - -/* const flags = { - de: ( - - - - - - ), - at: ( - - - - - - ) -}; */ - -const statusMapping = { - in_planning: 'in Planung', - paused: 'pausiert', - active: 'aktiv', -} - -// eslint-disable-next-line react/prop-types -const SubHeader = ({ type, status, url, title }) => ( -
-
- {status && ( -
- - - {statusMapping[status]} - -
- )} - {type && ( -
- - {type} - -
- )} -
-
- -
-
-) - -export default SubHeader diff --git a/src/Components/Profile/Subcomponents/ProfileTextForm.tsx b/src/Components/Profile/Subcomponents/ProfileTextForm.tsx new file mode 100644 index 00000000..0100f792 --- /dev/null +++ b/src/Components/Profile/Subcomponents/ProfileTextForm.tsx @@ -0,0 +1,53 @@ +import * as React from 'react' +import { TextAreaInput } from '../../Input' +import { FormState } from '../Templates/OnepagerForm' +import { getValue } from '../../../Utils/GetValue' +import { useEffect, useState } from 'react' + +export const ProfileTextForm = ({ + state, + setState, + dataField, + heading, + size, + hideInputLabel, +}: { + state: FormState + setState: React.Dispatch> + dataField?: string + heading: string + size: string + hideInputLabel: boolean +}) => { + const [field, setField] = useState(dataField || 'text') + + useEffect(() => { + if (!dataField) { + setField('text') + } + }, [dataField]) + + return ( +
+ + + setState((prevState) => ({ + ...prevState, + [field]: v, + })) + } + labelStyle={hideInputLabel ? 'tw-hidden' : ''} + containerStyle={size === 'full' ? 'tw-grow tw-h-full' : ''} + inputStyle={size === 'full' ? 'tw-h-full' : 'tw-h-24'} + /> +
+ ) +} diff --git a/src/Components/Profile/Subcomponents/ProfileTextView.tsx b/src/Components/Profile/Subcomponents/ProfileTextView.tsx new file mode 100644 index 00000000..0e58e217 --- /dev/null +++ b/src/Components/Profile/Subcomponents/ProfileTextView.tsx @@ -0,0 +1,26 @@ +import { Item } from '../../../types' +import { getValue } from '../../../Utils/GetValue' +import { TextView } from '../../Map' + +export const ProfileTextView = ({ + item, + dataField, + heading, + hideWhenEmpty, +}: { + item: Item + dataField: string + heading: string + hideWhenEmpty: boolean +}) => { + return ( +
+ {!(getValue(item, dataField) === '' && hideWhenEmpty) && ( +

{heading}

+ )} +
+ +
+
+ ) +} diff --git a/src/Components/Profile/Subcomponents/SocialShareBar.tsx b/src/Components/Profile/Subcomponents/SocialShareBar.tsx index 05b5af75..bd627431 100644 --- a/src/Components/Profile/Subcomponents/SocialShareBar.tsx +++ b/src/Components/Profile/Subcomponents/SocialShareBar.tsx @@ -1,19 +1,77 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { toast } from 'react-toastify' import SocialShareButton from './SocialShareButton' const SocialShareBar = ({ - // eslint-disable-next-line react/prop-types url, - // eslint-disable-next-line react/prop-types title, - // eslint-disable-next-line react/prop-types platforms = ['facebook', 'twitter', 'linkedin', 'xing', 'email'], +}: { + url: string + title: string + platforms?: string[] }) => { + const copyLink = () => { + navigator.clipboard + .writeText(url) + .then(() => { + toast.success('link copied to clipboard') + }) + .catch((error: never) => { + toast.error('Fehler beim Kopieren des Links: ', error) + }) + } return (
{platforms.map((platform) => ( ))} + {platforms.includes('email') && ( + copyLink()} + title='share link via email' + > + + + + + )} + {platforms.includes('clipboard') && ( +
copyLink()} + title='copy Link' + > + + + +
+ )}
) } diff --git a/src/Components/Profile/Subcomponents/SocialShareButton.tsx b/src/Components/Profile/Subcomponents/SocialShareButton.tsx index 110b6c2c..e77bd56e 100644 --- a/src/Components/Profile/Subcomponents/SocialShareButton.tsx +++ b/src/Components/Profile/Subcomponents/SocialShareButton.tsx @@ -42,19 +42,47 @@ const platformConfigs = { ), bgColor: '#026466', }, - email: { - shareUrl: 'mailto:?subject={title}&body={url}', + whatsapp: { + shareUrl: 'https://api.whatsapp.com/send?text={title}%20{url}', icon: ( - - + + ), - bgColor: '#444444', + bgColor: '#25D366', + }, + telegram: { + shareUrl: 'https://t.me/share/url?url={url}&text={title}', + icon: ( + + + + ), + bgColor: '#0088cc', }, } -// eslint-disable-next-line react/prop-types -const SocialShareButton = ({ platform, url, title }) => { +const SocialShareButton = ({ + platform, + url, + title, +}: { + platform: string + url: string + title: string +}) => { const config = platformConfigs[platform] if (!config) { @@ -76,6 +104,7 @@ const SocialShareButton = ({ platform, url, title }) => { color: 'white', backgroundColor: bgColor, }} + title={`share link on ${platform}`} > {React.cloneElement(icon, { className: 'tw-w-4 tw-h-4 tw-fill-current' })} diff --git a/src/Components/Profile/Templates/FlexForm.tsx b/src/Components/Profile/Templates/FlexForm.tsx new file mode 100644 index 00000000..6b570da4 --- /dev/null +++ b/src/Components/Profile/Templates/FlexForm.tsx @@ -0,0 +1,44 @@ +import * as React from 'react' +import { Item } from '../../../types' +import { FormState } from './OnepagerForm' +import { GroupSubheaderForm } from '../Subcomponents/GroupSubheaderForm' +import { ContactInfoForm } from '../Subcomponents/ContactInfoForm' +import { ProfileTextForm } from '../Subcomponents/ProfileTextForm' +import { ProfileStartEndForm } from '../Subcomponents/ProfileStartEndForm' + +const componentMap = { + groupSubheaders: GroupSubheaderForm, + texts: ProfileTextForm, + contactInfos: ContactInfoForm, + startEnd: ProfileStartEndForm, + // weitere Komponenten hier +} + +export const FlexForm = ({ + item, + state, + setState, +}: { + state: FormState + setState: React.Dispatch> + item: Item +}) => { + return ( +
+ {item.layer?.itemType.profileTemplate.map((templateItem) => { + const TemplateComponent = componentMap[templateItem.collection] + return TemplateComponent ? ( + + ) : ( +
Component not found
+ ) + })} +
+ ) +} diff --git a/src/Components/Profile/Templates/FlexView.tsx b/src/Components/Profile/Templates/FlexView.tsx new file mode 100644 index 00000000..f9243fee --- /dev/null +++ b/src/Components/Profile/Templates/FlexView.tsx @@ -0,0 +1,29 @@ +import { GroupSubHeaderView } from '../Subcomponents/GroupSubHeaderView' +import { ProfileTextView } from '../Subcomponents/ProfileTextView' +import { ContactInfoView } from '../Subcomponents/ContactInfoView' +import { Item } from '../../../types' +import { ProfileStartEndView } from '../Subcomponents/ProfileStartEndView' + +const componentMap = { + groupSubheaders: GroupSubHeaderView, + texts: ProfileTextView, + contactInfos: ContactInfoView, + startEnd: ProfileStartEndView, + // weitere Komponenten hier +} + +export const FlexView = ({ item }: { item: Item }) => { + console.log(item) + return ( +
+ {item.layer?.itemType.profileTemplate.map((templateItem) => { + const TemplateComponent = componentMap[templateItem.collection] + return TemplateComponent ? ( + + ) : ( +
Component not found
+ ) + })} +
+ ) +} diff --git a/src/Components/Profile/Templates/OnepagerForm.tsx b/src/Components/Profile/Templates/OnepagerForm.tsx index 7f76ac2b..865c9763 100644 --- a/src/Components/Profile/Templates/OnepagerForm.tsx +++ b/src/Components/Profile/Templates/OnepagerForm.tsx @@ -1,181 +1,42 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/no-unsafe-return */ import * as React from 'react' -import { useEffect } from 'react' import { Item, Tag } from '../../../types' -import { TextAreaInput, TextInput } from '../../Input' -import ComboBoxInput from '../../Input/ComboBoxInput' +import { TextAreaInput } from '../../Input' +import { GroupSubheaderForm } from '../Subcomponents/GroupSubheaderForm' +import { ContactInfoForm } from '../Subcomponents/ContactInfoForm' + +export interface FormState { + color: string + id: string + group_type: string + status: string + name: string + subname: string + text: string + contact: string + telephone: string + next_appointment: string + image: string + marker_icon: string + offers: Tag[] + needs: Tag[] + relations: Item[] +} export const OnepagerForm = ({ item, state, setState, }: { - state: { - color: string - id: string - groupType: string - status: string - name: string - subname: string - text: string - contact: string - telephone: string - nextAppointment: string - image: string - markerIcon: string - offers: Tag[] - needs: Tag[] - relations: Item[] - } + state: FormState setState: React.Dispatch> item: Item }) => { - useEffect(() => { - switch (state.groupType) { - case 'wuerdekompass': - setState((prevState) => ({ - ...prevState, - color: item.layer?.menuColor || '#1A5FB4', - markerIcon: 'group', - image: '59e6a346-d1ee-4767-9e42-fc720fb535c9', - })) - break - case 'themenkompass': - setState((prevState) => ({ - ...prevState, - color: '#26A269', - markerIcon: 'group', - image: '59e6a346-d1ee-4767-9e42-fc720fb535c9', - })) - break - case 'liebevoll.jetzt': - setState((prevState) => ({ - ...prevState, - color: '#E8B620', - markerIcon: 'liebevoll.jetzt', - image: 'e735b96c-507b-471c-8317-386ece0ca51d', - })) - - break - default: - break - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.groupType]) - - const typeMapping = [ - { value: 'wuerdekompass', label: 'Regional-Gruppe' }, - { value: 'themenkompass', label: 'Themen-Gruppe' }, - { value: 'liebevoll.jetzt', label: 'liebevoll.jetzt' }, - ] - const statusMapping = [ - { value: 'active', label: 'aktiv' }, - { value: 'in_planning', label: 'in Planung' }, - { value: 'paused', label: 'pausiert' }, - ] - return (
-
-
- - - setState((prevState) => ({ - ...prevState, - groupType: v, - })) - } - /> -
-
- - - setState((prevState) => ({ - ...prevState, - status: v, - })) - } - /> -
-
- -
- - - setState((prevState) => ({ - ...prevState, - contact: v, - })) - } - /> -
- -
- - - setState((prevState) => ({ - ...prevState, - telephone: v, - })) - } - /> -
- -
- - - setState((prevState) => ({ - ...prevState, - nextAppointment: v, - })) - } - inputStyle='tw-h-24' - /> -
+ +