- added new profile layout (not dynamic data linked yet)

- added new components around the profile
This commit is contained in:
Sebastian Stein 2024-06-25 09:46:26 +02:00
parent aa607da13b
commit aff509f1dd
7 changed files with 265 additions and 26 deletions

View 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;

View File

@ -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 &&

View 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;

View 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;

View 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;

View 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;

View File

@ -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>