mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
- added new profile layout (not dynamic data linked yet)
- added new components around the profile
This commit is contained in:
parent
aa607da13b
commit
aff509f1dd
35
src/Components/Profile/ContactInfo.tsx
Normal file
35
src/Components/Profile/ContactInfo.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
|
||||
const ContactInfo = ({ contact }) => (
|
||||
<div className="tw-bg-gray-100 tw-my-10 tw-p-6">
|
||||
<h2 className="tw-text-lg tw-font-semibold">Du hast Fragen?</h2>
|
||||
<div className="tw-mt-4 tw-flex tw-items-center">
|
||||
<div className="tw-w-20 tw-h-20 tw-bg-gray-200 tw-rounded-full tw-mr-5 tw-flex tw-items-center tw-justify-center">
|
||||
{contact.avatarSrc ? (
|
||||
<img src={contact.avatarSrc} alt={contact.name} className="tw-w-full tw-h-full tw-rounded-full" />
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" className="tw-w-6 tw-h-6"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
|
||||
<circle cx="12" cy="7" r="4"></circle>
|
||||
</svg>
|
||||
|
||||
)}
|
||||
</div>
|
||||
<div className="tw-text-sm">
|
||||
<p className="tw-font-semibold">{contact.name}</p>
|
||||
<a href={`mailto:${contact.email}`} className="tw-mt-2 tw-text-green-500 tw-flex tw-items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
className="tw-w-4 tw-h-4 tw-mr-1">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path>
|
||||
<polyline points="22,6 12,13 2,6"></polyline>
|
||||
</svg>
|
||||
|
||||
{contact.email}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ContactInfo;
|
||||
@ -22,6 +22,9 @@ import { useClusterRef } from '../Map/hooks/useClusterRef';
|
||||
import { useLeafletRefs } from '../Map/hooks/useLeafletRefs';
|
||||
import { getValue } from '../../Utils/GetValue';
|
||||
import { TagView } from '../Templates/TagView';
|
||||
import RelationCard from "./RelationCard";
|
||||
import ContactInfo from "./ContactInfo";
|
||||
import ProfileSubHeader from "./ProfileSubHeader";
|
||||
|
||||
export function OverlayItemProfile() {
|
||||
|
||||
@ -270,45 +273,96 @@ export function OverlayItemProfile() {
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
const d = {
|
||||
groupName: "Gruppe Berlin-Britz",
|
||||
location: "12347 Berlin-Britz",
|
||||
country: "Berlin, Deutschland",
|
||||
countryCode: "de",
|
||||
contact: {
|
||||
name: "Lisa Mustermann",
|
||||
email: "lisa.mustermann@gmx.de",
|
||||
avatarSrc: "https://cdn.prod.website-files.com/65c0d5530322d3f6f5f86099/65c0d5530322d3f6f5f86781_Andr%C3%A9.jpg" // optional
|
||||
},
|
||||
description: "Unsere KulturArche, ein historischer Frachtsegler...",
|
||||
relations: [
|
||||
{
|
||||
title: "KulturArche EALA",
|
||||
description: "Durchaus beeindruckt von der Ethik und der Arbeit...",
|
||||
imageSrc: "https://cdn.prod.website-files.com/65c0d5530322d3f6f5f86099/65c0d5530322d3f6f5f86767_IMG_20190302_173147.jpg"
|
||||
},
|
||||
// Add more projects as needed
|
||||
],
|
||||
url: window.location.href,
|
||||
title: "Gruppe Berlin-Britz"
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{item &&
|
||||
<MapOverlayPage key={item.id} className={`tw-mx-4 tw-mt-4 tw-max-h-[calc(100dvh-96px)] tw-h-[calc(100dvh-96px)] md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-min-w-80 tw-max-w-3xl !tw-left-0 sm:!tw-left-auto tw-top-0 tw-bottom-0 tw-transition-opacity tw-duration-500 ${!selectPosition ? 'tw-opacity-100 tw-pointer-events-auto' : 'tw-opacity-0 tw-pointer-events-none'}`}>
|
||||
<MapOverlayPage key={item.id} padding={false}
|
||||
className={`tw-mx-4 tw-mt-4 tw-max-h-[calc(100dvh-96px)] tw-h-[calc(100dvh-96px)] md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] tw-min-w-80 tw-max-w-3xl !tw-left-0 sm:!tw-left-auto tw-top-0 tw-bottom-0 tw-transition-opacity tw-duration-500 ${!selectPosition ? 'tw-opacity-100 tw-pointer-events-auto' : 'tw-opacity-0 tw-pointer-events-none'}`}>
|
||||
|
||||
<>
|
||||
<HeaderView api={item.layer?.api} item={item} deleteCallback={handleDelete} editCallback={() => navigate("/edit-item/" + item.id)} setPositionCallback={() => { map.closePopup(); setSelectPosition(item); navigate("/") }} big truncateSubname={false} />
|
||||
<div className="tw-px-6 tw-pt-6">
|
||||
<HeaderView api={item.layer?.api} item={item} deleteCallback={handleDelete} editCallback={() => navigate("/edit-item/" + item.id)} setPositionCallback={() => { map.closePopup(); setSelectPosition(item); navigate("/") }} big truncateSubname={false} />
|
||||
</div>
|
||||
|
||||
<div className='tw-h-full'>
|
||||
|
||||
{item.layer?.itemType.onepager &&
|
||||
<>
|
||||
<TextView item={item} />
|
||||
<div className='tw-grid tw-grid-cols-1 sm:tw-grid-cols-2 md:tw-grid-cols-1 lg:tw-grid-cols-1 xl:tw-grid-cols-1 2xl:tw-grid-cols-2'>
|
||||
{relations && relations.map(i =>
|
||||
<>
|
||||
<ProfileSubHeader
|
||||
location={d.location}
|
||||
country={d.country}
|
||||
countryCode={d.countryCode}
|
||||
url={d.url}
|
||||
title={d.title}
|
||||
/>
|
||||
|
||||
{d.contact && (
|
||||
<ContactInfo contact={d.contact}/>
|
||||
)}
|
||||
|
||||
<div key={i.id} className='tw-cursor-pointer tw-card tw-bg-base-200 tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-text-base-content tw-mx-4 tw-p-6 tw-mb-4' onClick={() => navigate('/item/' + i.id)}>
|
||||
<LinkedItemsHeaderView unlinkPermission={updatePermission} item={i} unlinkCallback={unlinkItem} loading={loading} />
|
||||
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
||||
<TextView truncate item={i} />
|
||||
</div>
|
||||
{/* Description Section */}
|
||||
<div className="tw-my-10 tw-px-6">
|
||||
<h2 className="tw-text-lg tw-font-semibold">Beschreibung</h2>
|
||||
<p className="tw-mt-2 tw-text-sm tw-text-gray-600">
|
||||
{d.description ?? 'Keine Beschreibung vorhanden'}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{updatePermission && <ActionButton collection="items" item={item} existingRelations={relations} triggerItemSelected={linkItem} colorField={item.layer.itemColorField}></ActionButton>}
|
||||
|
||||
</div>
|
||||
</>
|
||||
{/* Relations Section */}
|
||||
{d.relations && (
|
||||
<div className="tw-my-10 tw-px-6">
|
||||
<h2 className="tw-text-lg tw-font-semibold tw-mb-4">Projekte</h2>
|
||||
{d.relations.map((project, index) => (
|
||||
<RelationCard
|
||||
key={index}
|
||||
title={project.title}
|
||||
description={project.description}
|
||||
imageSrc={project.imageSrc}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
||||
{!item.layer?.itemType.onepager &&
|
||||
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-2 tw-mb-2">
|
||||
<input type="radio" name="my_tabs_2" role="tab" className={`tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`} aria-label="Info" checked={activeTab == 1 && true} onChange={() => updateActiveTab(1)} />
|
||||
<div role="tabpanel" className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden">
|
||||
{item.layer?.itemType.show_start_end &&
|
||||
<div className='tw-max-w-xs'><StartEndView item={item}></StartEndView></div>
|
||||
}
|
||||
<TextView item={item} />
|
||||
</div>
|
||||
{!item.layer?.itemType.onepager &&
|
||||
<div role="tablist" className="tw-tabs tw-tabs-lifted tw-mt-2 tw-mb-2">
|
||||
<input type="radio" name="my_tabs_2" role="tab"
|
||||
className={`tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]`}
|
||||
aria-label="Info" checked={activeTab == 1 && true}
|
||||
onChange={() => updateActiveTab(1)}/>
|
||||
<div role="tabpanel"
|
||||
className="tw-tab-content tw-bg-base-100 tw-rounded-box tw-h-[calc(100dvh-280px)] tw-overflow-y-auto fade tw-pt-2 tw-pb-4 tw-mb-4 tw-overflow-x-hidden">
|
||||
{item.layer?.itemType.show_start_end &&
|
||||
<div className='tw-max-w-xs'><StartEndView item={item}></StartEndView></div>
|
||||
}
|
||||
<TextView item={item}/>
|
||||
</div>
|
||||
|
||||
{item.layer?.itemType.offers_and_needs &&
|
||||
|
||||
|
||||
34
src/Components/Profile/ProfileSubHeader.tsx
Normal file
34
src/Components/Profile/ProfileSubHeader.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import SocialShareBar from './SocialShareBar';
|
||||
|
||||
|
||||
const flags = {
|
||||
de: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 3" className="tw-w-5 tw-h-3">
|
||||
<rect width="5" height="3" fill="#FFCE00"/>
|
||||
<rect width="5" height="2" fill="#DD0000"/>
|
||||
<rect width="5" height="1" fill="#000000"/>
|
||||
</svg>
|
||||
),
|
||||
at: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 3" className="tw-w-5 tw-h-3">
|
||||
<rect width="5" height="3" fill="#ED2939"/>
|
||||
<rect width="5" height="2" fill="#FFFFFF"/>
|
||||
<rect width="5" height="1" fill="#ED2939"/>
|
||||
</svg>
|
||||
)
|
||||
};
|
||||
|
||||
const SubHeader = ({ location, country, countryCode, url, title }) => (
|
||||
<div className="tw-px-6">
|
||||
<div className="tw-flex tw-items-center tw-mt-6">
|
||||
<span className="tw-text-sm tw-text-gray-600">{location}</span>
|
||||
<span className="tw-ml-6">{flags[countryCode] || null}</span>
|
||||
<span className="tw-text-sm tw-text-gray-600 tw-ml-2">{country}</span>
|
||||
</div>
|
||||
<div className="tw-mt-4">
|
||||
<SocialShareBar url={url} title={title} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SubHeader;
|
||||
16
src/Components/Profile/RelationCard.tsx
Normal file
16
src/Components/Profile/RelationCard.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
|
||||
const RelationCard = ({ title, description, imageSrc }) => (
|
||||
<div className={`tw-mb-6 ${imageSrc ? 'md:tw-flex md:tw-space-x-4' : ''}`}>
|
||||
{imageSrc && (
|
||||
<div className="md:tw-w-1/2 tw-mb-4 md:tw-mb-0">
|
||||
<img src={imageSrc} alt={title} className="tw-w-full tw-h-32 tw-object-cover" />
|
||||
</div>
|
||||
)}
|
||||
<div className={imageSrc ? 'md:tw-w-1/2' : 'tw-w-full'}>
|
||||
<h3 className="tw-text-lg tw-font-semibold">{title}</h3>
|
||||
<p className="tw-mt-2 tw-text-sm tw-text-gray-600">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default RelationCard;
|
||||
20
src/Components/Profile/SocialShareBar.tsx
Normal file
20
src/Components/Profile/SocialShareBar.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import SocialShareButton from './SocialShareButton';
|
||||
|
||||
const SocialShareBar = ({ url, title, platforms = ['facebook', 'twitter', 'linkedin', 'xing', 'email'] }) => {
|
||||
return (
|
||||
<div className="tw-flex tw-items-center tw-justify-end tw-space-x-2">
|
||||
<span className="tw-text-sm tw-font-medium tw-text-gray-700">Teilen:</span>
|
||||
<div className="tw-flex tw-space-x-2">
|
||||
{platforms.map((platform) => (
|
||||
<SocialShareButton
|
||||
platform={platform}
|
||||
url={url}
|
||||
title={title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SocialShareBar;
|
||||
80
src/Components/Profile/SocialShareButton.tsx
Normal file
80
src/Components/Profile/SocialShareButton.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import * as React from 'react';
|
||||
|
||||
const platformConfigs = {
|
||||
facebook: {
|
||||
shareUrl: 'https://www.facebook.com/sharer/sharer.php?u={url}',
|
||||
icon: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
||||
<path d="M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#3b5998'
|
||||
},
|
||||
twitter: {
|
||||
shareUrl: 'https://twitter.com/intent/tweet?text={title}:%20{url}',
|
||||
icon: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
||||
<path d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#55acee'
|
||||
},
|
||||
linkedin: {
|
||||
shareUrl: 'http://www.linkedin.com/shareArticle?mini=true&url={url}&title={title}',
|
||||
icon: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
||||
<path d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z" />
|
||||
<circle cx="4" cy="4" r="2" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#4875b4'
|
||||
},
|
||||
xing: {
|
||||
shareUrl: 'https://www.xing-share.com/app/user?op=share;sc_p=xing-share;url={url}',
|
||||
icon: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
||||
<path d="M18.188 0c-.517 0-.741.325-.927.66 0 0-7.455 13.224-7.702 13.657.015.024 4.919 9.023 4.919 9.023.17.308.436.66.967.66h3.454c.211 0 .375-.078.463-.22.089-.151.089-.346-.009-.536l-4.879-8.916c-.004-.006-.004-.016 0-.022L22.139.756c.095-.191.097-.387.006-.535C22.056.078 21.894 0 21.686 0h-3.498zM3.648 4.74c-.211 0-.385.074-.473.216-.09.149-.078.339.02.531l2.34 4.05c.004.01.004.016 0 .021L1.86 16.051c-.099.188-.093.381 0 .529.085.142.239.234.45.234h3.461c.518 0 .766-.348.945-.667l3.734-6.609-2.378-4.155c-.172-.315-.434-.659-.962-.659H3.648v.016z" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#026466'
|
||||
},
|
||||
email: {
|
||||
shareUrl: 'mailto:?subject={title}&body={url}',
|
||||
icon: (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white">
|
||||
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
|
||||
</svg>
|
||||
),
|
||||
bgColor: '#444444'
|
||||
}
|
||||
};
|
||||
|
||||
const SocialShareButton = ({ platform, url, title }) => {
|
||||
const config = platformConfigs[platform];
|
||||
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { shareUrl, icon, bgColor } = config;
|
||||
const finalShareUrl = shareUrl
|
||||
.replace('{url}', encodeURIComponent(url))
|
||||
.replace('{title}', encodeURIComponent(title));
|
||||
|
||||
return (
|
||||
<a
|
||||
href={finalShareUrl}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='tw-w-8 tw-h-8 tw-rounded-full tw-flex tw-items-center tw-justify-center tw-text-white'
|
||||
style={{
|
||||
color: 'white',
|
||||
backgroundColor: bgColor
|
||||
}}
|
||||
>
|
||||
{React.cloneElement(icon, { className: 'tw-w-4 tw-h-4 tw-fill-current' })}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default SocialShareButton;
|
||||
@ -3,7 +3,7 @@ import * as L from 'leaflet';
|
||||
import * as React from 'react'
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export function MapOverlayPage({ children, className, backdrop, card = true }: { children: React.ReactNode, className?: string, backdrop?: boolean, card?:boolean }) {
|
||||
export function MapOverlayPage({ children, className, backdrop, card = true, padding = true }: { children: React.ReactNode, className?: string, backdrop?: boolean, card?:boolean, padding?:boolean }) {
|
||||
|
||||
|
||||
const closeScreen = () => {
|
||||
@ -32,7 +32,7 @@ export function MapOverlayPage({ children, className, backdrop, card = true }: {
|
||||
return (
|
||||
<div className={`tw-absolute tw-h-full tw-w-full tw-m-auto ${backdrop && "tw-z-[2000]"}`}>
|
||||
<div ref={backdropRef} className={`${backdrop && "tw-backdrop-brightness-75"} tw-h-full tw-w-full tw-grid tw-place-items-center tw-m-auto`} >
|
||||
<div ref={overlayRef} className={`${card && "tw-card tw-card-body"} tw-shadow-xl tw-bg-base-100 tw-p-6 ${className && className} ${!backdrop && "tw-z-[2000]"} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}>
|
||||
<div ref={overlayRef} className={`${card && "tw-card tw-card-body"} ${padding ? "tw-p-6" : "tw-p-0"} tw-shadow-xl tw-bg-base-100 ${className && className} ${!backdrop && "tw-z-[2000]"} tw-absolute tw-top-0 tw-bottom-0 tw-right-0 tw-left-0 tw-m-auto`}>
|
||||
{children}
|
||||
<button className="tw-btn tw-btn-sm tw-btn-circle tw-btn-ghost tw-absolute tw-right-2 tw-top-2" onClick={() => closeScreen()}>✕</button>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user