diff --git a/src/Components/Map/Subcomponents/Controls/GratitudeControl.tsx b/src/Components/Map/Subcomponents/Controls/GratitudeControl.tsx new file mode 100644 index 00000000..a8f7109b --- /dev/null +++ b/src/Components/Map/Subcomponents/Controls/GratitudeControl.tsx @@ -0,0 +1,29 @@ +import { useNavigate } from "react-router-dom" +import { useAuth } from "../../../Auth"; + +export const GratitudeControl = () => { + const navigate = useNavigate(); + const {isAuthenticated} = useAuth(); + + if(isAuthenticated) return ( +
+ { + + +
{ + navigate("/select-user") + }}> + + + + + + +
+ + } + +
+ ) + else return (<>); +} diff --git a/src/Components/Map/UtopiaMap.tsx b/src/Components/Map/UtopiaMap.tsx index f3be3e10..016d4ba9 100644 --- a/src/Components/Map/UtopiaMap.tsx +++ b/src/Components/Map/UtopiaMap.tsx @@ -9,9 +9,8 @@ import AddButton from "./Subcomponents/AddButton"; import { useEffect, useState } from "react"; import { ItemFormPopupProps } from "./Subcomponents/ItemFormPopup"; import { SearchControl } from "./Subcomponents/Controls/SearchControl"; -// import { QuestControl } from "./Subcomponents/Controls/QuestControl"; import { Control } from "./Subcomponents/Controls/Control"; -import { Outlet, useLocation, useNavigate } from "react-router-dom"; +import { Outlet } from "react-router-dom"; import { TagsControl } from "./Subcomponents/Controls/TagsControl"; import { useSelectPosition, useSetMapClicked, useSetSelectPosition } from "./hooks/useSelectPosition"; import { useClusterRef, useSetClusterRef } from "./hooks/useClusterRef"; @@ -20,6 +19,7 @@ import { FilterControl } from "./Subcomponents/Controls/FilterControl"; import { LayerControl } from "./Subcomponents/Controls/LayerControl"; import { useLayers } from "./hooks/useLayers"; import { useAddVisibleLayer } from "./hooks/useFilter"; +import { GratitudeControl } from "./Subcomponents/Controls/GratitudeControl"; import { SelectPosition } from "./Subcomponents/SelectPosition"; // for refreshing map on resize (needs to be implemented) @@ -63,21 +63,13 @@ function UtopiaMap({ const selectNewItemPosition = useSelectPosition(); const setSelectNewItemPosition = useSetSelectPosition(); - const location = useLocation(); const setClusterRef = useSetClusterRef(); const clusterRef = useClusterRef(); const setMapClicked = useSetMapClicked(); const [itemFormPopup, setItemFormPopup] = useState(null); - const [embedded, setEmbedded] = useState(true) - useEffect(() => { - let params = new URLSearchParams(location.search); - let urlPosition = params.get("position"); - let embedded = params.get("embedded"); - embedded != "true" && setEmbedded(false) - }, [location]); const layers = useLayers(); @@ -113,6 +105,7 @@ function UtopiaMap({ {showFilterControl && } {/*todo: needed layer handling is located LayerControl*/} {showLayerControl && } + {} }) { const [item, setItem] = useState() const [updatePermission, setUpdatePermission] = useState(false); @@ -40,6 +40,23 @@ export function ProfileView({ userType }: { userType: string }) { const clusterRef = useClusterRef(); const leafletRefs = useLeafletRefs(); + const [attestations, setAttestations] = useState>([]); + + useEffect(() => { + if (attestationApi) { + attestationApi.getItems() + .then(value => { + console.log(value); + + setAttestations(value); + }) + .catch(error => { + console.error("Error fetching items:", error); + }); + } + }, [attestationApi]) + + useEffect(() => { const itemId = location.pathname.split("/")[2]; const item = items.find(i => i.id === itemId); @@ -140,7 +157,7 @@ export function ProfileView({ userType }: { userType: string }) { } {template == "tabs" && - linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)}/> + linkItem(id, item, updateItem)} unlinkItem={(id) => unlinkItem(id, item, updateItem)}/> } diff --git a/src/Components/Profile/Templates/TabsForm.tsx b/src/Components/Profile/Templates/TabsForm.tsx index 35e48e2a..9380faf6 100644 --- a/src/Components/Profile/Templates/TabsForm.tsx +++ b/src/Components/Profile/Templates/TabsForm.tsx @@ -17,6 +17,7 @@ export const TabsForm = ({ item, state, setState, updatePermission, linkItem, un setActiveTab(id); let params = new URLSearchParams(window.location.search); + params.set("tab", `${id}`); const newUrl = location.pathname + "?" + params.toString(); window.history.pushState({}, '', newUrl); diff --git a/src/Components/Profile/Templates/TabsView.tsx b/src/Components/Profile/Templates/TabsView.tsx index 15016607..0d36a064 100644 --- a/src/Components/Profile/Templates/TabsView.tsx +++ b/src/Components/Profile/Templates/TabsView.tsx @@ -5,9 +5,13 @@ import { ActionButton } from '../Subcomponents/ActionsButton' import { useCallback, useEffect, useRef, useState } from 'react' import { useAddFilterTag } from '../../Map/hooks/useFilter' import { Item, Tag } from 'utopia-ui/dist/types' -import { useNavigate } from 'react-router-dom' +import { Link, useNavigate } from 'react-router-dom' +import { useItems } from '../../Map/hooks/useItems' +import { useAssetApi } from '../../AppShell/hooks/useAssets' +import { timeAgo } from '../../../Utils/TimeAgo' +import { useAuth } from '../../Auth' -export const TabsView = ({ item, offers, needs, relations, updatePermission, loading, linkItem, unlinkItem, setUrlParams }: { item: Item, offers: Array, needs: Array, relations: Array, updatePermission: boolean, loading: boolean, linkItem: (id: string) => Promise, unlinkItem: (id: string) => Promise , setUrlParams: any}) => { +export const TabsView = ({ attestations, userType, item, offers, needs, relations, updatePermission, loading, linkItem, unlinkItem, setUrlParams }: { attestations: Array, userType: string, item: Item, offers: Array, needs: Array, relations: Array, updatePermission: boolean, loading: boolean, linkItem: (id: string) => Promise, unlinkItem: (id: string) => Promise, setUrlParams: any }) => { const addFilterTag = useAddFilterTag(); const [activeTab, setActiveTab] = useState(); @@ -15,12 +19,18 @@ export const TabsView = ({ item, offers, needs, relations, updatePermission, loa const [addItemPopupType, setAddItemPopupType] = useState(""); + const items = useItems(); + const assetsApi = useAssetApi(); + const getUserProfile = (id: string) => { + return items.find(i => i.user_created.id === id && i.layer?.itemType.name === userType) + } + useEffect(() => { - scroll(); + scroll(); }, [addItemPopupType]) - + function scroll() { - tabRef.current?.scrollIntoView(); + tabRef.current?.scrollIntoView(); } const tabRef = useRef(null); @@ -46,7 +56,7 @@ export const TabsView = ({ item, offers, needs, relations, updatePermission, loa
updateActiveTab(1)} />
@@ -55,14 +65,63 @@ export const TabsView = ({ item, offers, needs, relations, updatePermission, loa }
- +
+ {item.layer?.itemType.questlog && + <> + updateActiveTab(2)} /> +
+ + + {attestations + .filter(a => a.to.some(t => t.directus_users_id == item.user_created.id)) + .sort((a, b) => new Date(b.date_created).getTime() - new Date(a.date_created).getTime()) + .map((a, i) => ( + + + + + + ))} + +
+
+ {a.emoji} +
+
+
{a.text}
+
+ +
+
+
+ Avatar Tailwind CSS Component +
+
+
+
{getUserProfile(a.user_created.id)?.name}
+
{timeAgo(a.date_created)}
+
+
+ +
+ +
+ + } {item.layer?.itemType.offers_and_needs && <> - updateActiveTab(3)} /> + updateActiveTab(3)} />
@@ -100,7 +159,7 @@ export const TabsView = ({ item, offers, needs, relations, updatePermission, loa {item.layer?.itemType.relations && <> - updateActiveTab(7)} /> + updateActiveTab(7)} />
diff --git a/src/Components/Templates/AttestationForm.tsx b/src/Components/Templates/AttestationForm.tsx new file mode 100644 index 00000000..399eb9fc --- /dev/null +++ b/src/Components/Templates/AttestationForm.tsx @@ -0,0 +1,108 @@ + +import { MapOverlayPage } from './MapOverlayPage' +import { useItems } from '../Map/hooks/useItems' +import { useAssetApi } from '../AppShell/hooks/useAssets' +import { EmojiPicker } from './EmojiPicker'; +import { Link, useNavigate } from 'react-router-dom'; +import { useRef, useState } from 'react'; +import { Item, ItemsApi } from '../../types'; +import { useEffect } from 'react'; +import { toast } from 'react-toastify'; + +export const AttestationForm = ({api}:{api?:ItemsApi}) => { + + const items = useItems(); + const assetsApi = useAssetApi(); + const [users, setUsers] = useState>(); + const navigate = useNavigate(); + + useEffect(() => { + let params = new URLSearchParams(location.search); + let to_user_ids = params.get("to"); + setUsers(items.filter(i => to_user_ids?.includes(i.id))) + }, [items, location]) + + const [inputValue, setInputValue] = useState(''); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.style.width = 'auto'; + inputRef.current.style.width = `${inputRef.current.scrollWidth+20}px`; + } + }, [inputValue]); + + const handleChange = (event: React.ChangeEvent) => { + setInputValue(event.target.value); + }; + + const sendAttestation = async () => { + const to : Array = []; + users?.map(u => to.push({ directus_users_id: u.user_created.id })); + + + api?.createItem && toast.promise( + api.createItem({ + text: inputValue, + emoji: selectedEmoji, + color: selectedColor, + shape: selectedShape, + to: to + }), { + pending: 'creating attestation ...', + success: 'Attestation created', + error: { + render({ data }) { + return `${data}` + }, + }, + }) + .then( () => navigate("/item/" + items.find(i => i.user_created.id===to[0].directus_users_id && i.layer?.itemType.name==="player")?.id+"?tab=2") + ) + + } + + const [selectedEmoji, setSelectedEmoji] = useState('select badge'); + const [selectedShape, setSelectedShape] = useState('circle'); + const [selectedColor, setSelectedColor] = useState('#fff0d6'); + + + return ( + +
Gratitude
+
to
+
+ {users && users.map((u, k) => ( +
+ {u.image ?
+
+ Avatar +
+
: +
} +
+
{u.name}
+
+
+ ), ", ")} +
+ +
+
+
+ +
+
+
+ +
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/Components/Templates/EmojiPicker.tsx b/src/Components/Templates/EmojiPicker.tsx new file mode 100644 index 00000000..4f7f7be5 --- /dev/null +++ b/src/Components/Templates/EmojiPicker.tsx @@ -0,0 +1,93 @@ +import { useState } from 'react'; + +export const EmojiPicker = ({selectedEmoji, selectedColor, selectedShape, setSelectedEmoji, setSelectedColor, setSelectedShape}) => { + + + + const [isOpen, setIsOpen] = useState(false); + + const emojis = [ + 'โค๏ธ', '๐Ÿ™', '๐Ÿ‘', '๐ŸŒป',, 'โœจ', 'โ˜€๏ธ', + '๐Ÿ”ฅ', '๐Ÿชต', '๐Ÿ’ง', '๐ŸŽถ', '๐ŸŽจ','๐Ÿ„', + '๐Ÿ“', 'โœ‰๏ธ', '๐Ÿงฉ','๐Ÿ’ก', '๐ŸŽ“', '๐Ÿ’ฌ', + '๐Ÿ› ', '๐Ÿ’ป', '๐Ÿ•น', '๐Ÿ–จ', '๐Ÿš', '๐Ÿ›’', + 'โšฝ๏ธ', '๐Ÿงต', '๐Ÿ‘€', '๐ŸŒฑ', + '๐Ÿ•', '๐Ÿ’ช', '๐ŸŽ', '๐Ÿน', + '๐Ÿฅ•', '๐Ÿฅ‡', '๐Ÿฅˆ', '๐Ÿฅ‰']; + const shapes = ["squircle", "circle", "hexagon-2"]; + + const colors = ["#FF99C8", "#fff0d6", "#FCF6BD", "#D0F4DE", "#A9DEF9", "#E4C1F9", "#de324c", "#f4895f", "#f8e16f", "#95cf92", "#369acc", "#9656a2"] + + + const toggleDropdown = () => { + setIsOpen(!isOpen); + }; + + const selectEmoji = (emoji) => { + setSelectedEmoji(emoji); + setIsOpen(false); + }; + + const selectShape = (shape) => { + setSelectedShape(shape); + setIsOpen(false); + }; + + const selectColor = (color) => { + setSelectedColor(color); + setIsOpen(false); + }; + + + + return ( + <> +
+ {selectedEmoji} +
+ + {isOpen && ( +
+
+ {emojis.map((emoji) => ( + + ))} +
+
+
+ {shapes.map(shape => ( +
selectShape(shape)}> +
+
+ ))} +
+
+
+ {colors.map(color => ( +
selectColor(color)}> +
+
+ ))} +
+
+ + )} + + ); +}; + diff --git a/src/Components/Templates/SelectUser.tsx b/src/Components/Templates/SelectUser.tsx new file mode 100644 index 00000000..c43d62bb --- /dev/null +++ b/src/Components/Templates/SelectUser.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react' +import { MapOverlayPage } from './MapOverlayPage' +import { useItems } from '../Map/hooks/useItems' +import { useAssetApi } from '../AppShell/hooks/useAssets' +import { Link } from 'react-router-dom' + +export const SelectUser = ({ userType }: { userType: string }) => { + + const items = useItems(); + const users = items.filter(i => i.layer?.itemType.name == userType) + const assetsApi = useAssetApi(); + + const [selectedUsers, setSelectedUsers] = useState>([]); + + return ( + + +
Gratitude to ...
+ + + {/* Team Member list in table format loaded constant */} +
+ + + { + users.map((u, k) => { + return ( + + + + + ) + }) + } + +
+ setSelectedUsers(prev => [...prev, u.id])} className="tw-checkbox tw-checkbox-sm" /> + +
+ {u.image ?
+
+ Avatar +
+
: +
} +
+
{u.name}
+
+
+
+
+
u,",")}>
+
+ ) +} \ No newline at end of file diff --git a/src/Components/Templates/index.tsx b/src/Components/Templates/index.tsx index bd2720a6..cbb8fd57 100644 --- a/src/Components/Templates/index.tsx +++ b/src/Components/Templates/index.tsx @@ -1,5 +1,9 @@ +import { AttestationForm } from './AttestationForm' + export {CardPage} from './CardPage' export {TitleCard} from './TitleCard' export {MapOverlayPage} from './MapOverlayPage' export {MoonCalendar} from './MoonCalendar' -export {OverlayItemsIndexPage} from "./OverlayItemsIndexPage" \ No newline at end of file +export {SelectUser} from "./SelectUser" +export {OverlayItemsIndexPage} from "./OverlayItemsIndexPage" +export {AttestationForm} from "./AttestationForm" diff --git a/src/index.css b/src/index.css index 9fae544a..0790b43c 100644 --- a/src/index.css +++ b/src/index.css @@ -52,6 +52,10 @@ } +.placeholder-center::placeholder { + text-align: center; +} + .custom-file-upload { cursor: pointer; } diff --git a/src/index.tsx b/src/index.tsx index 7afe478e..2137b4dc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3,7 +3,7 @@ export {AppShell, Content, SideBar, Sitemap } from "./Components/AppShell" export {AuthProvider, useAuth, LoginPage, SignupPage, RequestPasswordPage, SetNewPasswordPage} from "./Components/Auth" export {UserSettings, ProfileView, ProfileForm} from './Components/Profile' export {Quests, Modal} from './Components/Gaming' -export {TitleCard, CardPage, MapOverlayPage, OverlayItemsIndexPage, MoonCalendar } from './Components/Templates' +export {TitleCard, CardPage, MapOverlayPage, OverlayItemsIndexPage, MoonCalendar, SelectUser, AttestationForm } from './Components/Templates' export {TextInput, TextAreaInput, SelectBox} from './Components/Input' import "./index.css" diff --git a/tailwind.config.js b/tailwind.config.js index 58613143..79de417a 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -4,6 +4,25 @@ module.exports = { "./src/**/*.{js,jsx,ts,tsx}", "./node_modules/tw-elements/dist/js/**/*.js" ], + safelist: [ + 'tw-mask-squircle', + 'tw-mask-circle', + 'tw-mask-hexagon-2', + 'tw-mask-decagon', + 'tw-bg-[#FF99C8]', + 'tw-bg-[#fff0d6]', + 'tw-bg-[#FCF6BD]', + 'tw-bg-[#D0F4DE]', + 'tw-bg-[#A9DEF9]', + 'tw-bg-[#E4C1F9]', + 'tw-bg-[#de324c]', + 'tw-bg-[#f4895f]', + 'tw-bg-[#f8e16f]', + 'tw-bg-[#95cf92]', + 'tw-bg-[#369acc]', + 'tw-bg-[#9656a2]', + + ], theme: { extend: { zIndex: { @@ -31,7 +50,7 @@ module.exports = { 'sans': ["Helvetica", "sans-serif", 'Roboto'], }, fontSize: { - 'map': "13px" + 'map': "13px" }, lineHeight: { 'map': "1.4em" @@ -54,17 +73,18 @@ module.exports = { ], daisyui: { themes: ["light", "dark", "cupcake", "retro", "cyberpunk", "aqua", - { - docutopia: { - "primary": "#8e00ff", - "secondary": "#00bb7a", - "accent": "#006aff", - "neutral": "#231502", - "base-content": "#ffad6b", - "base-100": "#440844", - }, - },] + { + docutopia: { + "primary": "#8e00ff", + "secondary": "#00bb7a", + "accent": "#006aff", + "neutral": "#231502", + "base-content": "#ffad6b", + "base-100": "#440844", + }, + },] }, prefix: 'tw-', content: ['./src/**/*.{js,jsx,ts,tsx}'], -} + +} \ No newline at end of file