modularized ResearchLive
by introducing reusable components (DreamyCard
, HeroSection
, SectionHeader
, etc.), reducing code duplication and improving maintainability
Signed-off-by: Matthias Puchstein <matthias@puchstein.bayern>
This commit is contained in:
27
src/components/dreamarchive/DreamyCard.tsx
Normal file
27
src/components/dreamarchive/DreamyCard.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import React, {ReactNode} from 'react';
|
||||||
|
import {getBackgroundStyle} from '../../styles/StyleUtils';
|
||||||
|
|
||||||
|
interface DreamyCardProps {
|
||||||
|
children: ReactNode;
|
||||||
|
color?: 'purple' | 'blue' | 'violet' | 'emerald' | 'amber' | 'rose' | 'pink-red' | 'cta';
|
||||||
|
className?: string;
|
||||||
|
padding?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DreamyCard: React.FC<DreamyCardProps> = ({
|
||||||
|
children,
|
||||||
|
color = 'purple',
|
||||||
|
className = '',
|
||||||
|
padding = 'p-6'
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`dreamy-card ${padding} ${className}`}
|
||||||
|
style={getBackgroundStyle(color)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DreamyCard;
|
31
src/components/dreamarchive/HeroSection.tsx
Normal file
31
src/components/dreamarchive/HeroSection.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface HeroSectionProps {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
containerTitle: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const HeroSection: React.FC<HeroSectionProps> = ({title, subtitle, containerTitle, description}) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center mb-12 sm:mb-16 relative z-10">
|
||||||
|
<div className="animate-pulse flex flex-col items-center mb-8 sm:mb-12">
|
||||||
|
<h1 className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 dream-title">{title}</h1>
|
||||||
|
<p className="text-lg sm:text-xl dreamy-text">{subtitle}</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dream-container floating max-w-3xl mx-auto backdrop-blur-sm bg-white/10 dark:bg-white/5 rounded-3xl p-4 sm:p-6 md:p-8"
|
||||||
|
style={{animationDelay: '0.2s'}}>
|
||||||
|
<div className="flex flex-col sm:flex-row justify-center items-center mb-3 sm:mb-4">
|
||||||
|
<h2 className="dream-title text-xl sm:text-2xl">{containerTitle}</h2>
|
||||||
|
</div>
|
||||||
|
<p className="mb-4 sm:mb-6 md:mb-8 text-sm sm:text-base md:text-lg" style={{color: 'var(--text)'}}>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HeroSection;
|
26
src/components/dreamarchive/IconWithBackground.tsx
Normal file
26
src/components/dreamarchive/IconWithBackground.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React, {ReactElement} from 'react';
|
||||||
|
|
||||||
|
interface IconWithBackgroundProps {
|
||||||
|
icon: ReactElement;
|
||||||
|
color: string;
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconWithBackground: React.FC<IconWithBackgroundProps> = ({
|
||||||
|
icon,
|
||||||
|
color,
|
||||||
|
size = 28,
|
||||||
|
className = ''
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={`p-3 bg-${color}-500/20 rounded-full ${className}`}>
|
||||||
|
{React.cloneElement(icon, {
|
||||||
|
className: `text-${color}-600 dark:text-${color}-400`,
|
||||||
|
size: size
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IconWithBackground;
|
32
src/components/dreamarchive/NavigationLinks.tsx
Normal file
32
src/components/dreamarchive/NavigationLinks.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {NavLink} from 'react-router-dom';
|
||||||
|
|
||||||
|
interface NavigationLinksProps {
|
||||||
|
previousLink?: {
|
||||||
|
to: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
nextLink?: {
|
||||||
|
to: string;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NavigationLinks: React.FC<NavigationLinksProps> = ({previousLink, nextLink}) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-wrap justify-between items-center">
|
||||||
|
{previousLink && (
|
||||||
|
<NavLink to={previousLink.to} className="dreamy-card p-3 inline-flex items-center">
|
||||||
|
<span className="mr-2">←</span> {previousLink.text}
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
{nextLink && (
|
||||||
|
<NavLink to={nextLink.to} className="dreamy-card p-3 inline-flex items-center">
|
||||||
|
{nextLink.text} <span className="ml-2">→</span>
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NavigationLinks;
|
48
src/components/dreamarchive/ResearcherInterviewCard.tsx
Normal file
48
src/components/dreamarchive/ResearcherInterviewCard.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {FaUserMd} from 'react-icons/fa';
|
||||||
|
import {getBackgroundStyle, getTextStyle} from '../../styles/StyleUtils';
|
||||||
|
import IconWithBackground from './IconWithBackground';
|
||||||
|
import VideoPlayer from './VideoPlayer';
|
||||||
|
|
||||||
|
interface ResearcherInterviewCardProps {
|
||||||
|
interview: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
institution: string;
|
||||||
|
specialty: string;
|
||||||
|
topics: string[];
|
||||||
|
color: string;
|
||||||
|
videoId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ResearcherInterviewCard: React.FC<ResearcherInterviewCardProps> = ({interview}) => {
|
||||||
|
return (
|
||||||
|
<div className="dreamy-card p-6" style={getBackgroundStyle(interview.color)}>
|
||||||
|
<div className="flex items-start mb-4">
|
||||||
|
<IconWithBackground
|
||||||
|
icon={<FaUserMd/>}
|
||||||
|
color={interview.color}
|
||||||
|
size={28}
|
||||||
|
className="mr-4 mt-1"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold dreamy-text">{interview.name}</h3>
|
||||||
|
<p className="text-xs mb-1" style={getTextStyle('muted')}>{interview.institution}</p>
|
||||||
|
<p className="text-sm mb-3">{interview.specialty}</p>
|
||||||
|
|
||||||
|
<VideoPlayer color={interview.color}/>
|
||||||
|
|
||||||
|
<h4 className="font-bold mt-3 mb-1">Themen im Interview:</h4>
|
||||||
|
<ul className="list-disc list-inside text-sm space-y-1">
|
||||||
|
{interview.topics.map((topic, index) => (
|
||||||
|
<li key={index}>{topic}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResearcherInterviewCard;
|
14
src/components/dreamarchive/SectionHeader.tsx
Normal file
14
src/components/dreamarchive/SectionHeader.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface SectionHeaderProps {
|
||||||
|
title: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SectionHeader: React.FC<SectionHeaderProps> = ({title, className = ''}) => {
|
||||||
|
return (
|
||||||
|
<h2 className={`text-2xl font-bold mb-6 dream-title ${className}`}>{title}</h2>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SectionHeader;
|
71
src/components/dreamarchive/StudyCard.tsx
Normal file
71
src/components/dreamarchive/StudyCard.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {FaFlask} from 'react-icons/fa';
|
||||||
|
import {getBackgroundStyle, getTextStyle} from '../../styles/StyleUtils';
|
||||||
|
import IconWithBackground from './IconWithBackground';
|
||||||
|
|
||||||
|
interface StudyCardProps {
|
||||||
|
study: {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
institution: string;
|
||||||
|
status: string;
|
||||||
|
statusColor: string;
|
||||||
|
participants: {
|
||||||
|
current: number;
|
||||||
|
target: number;
|
||||||
|
};
|
||||||
|
endDate: string;
|
||||||
|
description: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StudyCard: React.FC<StudyCardProps> = ({study}) => {
|
||||||
|
return (
|
||||||
|
<div className="dreamy-card p-6" style={getBackgroundStyle(study.color)}>
|
||||||
|
<div className="flex items-center mb-4">
|
||||||
|
<IconWithBackground
|
||||||
|
icon={<FaFlask/>}
|
||||||
|
color={study.color}
|
||||||
|
size={28}
|
||||||
|
className="mr-4"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold dreamy-text">{study.title}</h3>
|
||||||
|
<p className="text-xs" style={getTextStyle('muted')}>{study.institution}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white/20 dark:bg-black/20 p-4 rounded-lg mb-4">
|
||||||
|
<div className="flex justify-between mb-2">
|
||||||
|
<span className="text-sm font-bold">Status:</span>
|
||||||
|
<span
|
||||||
|
className={`text-sm bg-${study.statusColor}-100 dark:bg-${study.statusColor}-900 text-${study.statusColor}-800 dark:text-${study.statusColor}-200 px-2 py-0.5 rounded-full`}>
|
||||||
|
{study.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between mb-2">
|
||||||
|
<span className="text-sm font-bold">Teilnehmer:</span>
|
||||||
|
<span className="text-sm">
|
||||||
|
{study.participants.current.toLocaleString()} / {study.participants.target.toLocaleString()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="text-sm font-bold">Enddatum:</span>
|
||||||
|
<span className="text-sm">{study.endDate}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm mb-4">
|
||||||
|
{study.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className={`w-full py-2 px-4 bg-${study.color}-500 hover:bg-${study.color}-600 text-white rounded-lg transition-colors`}>
|
||||||
|
Teilnehmen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudyCard;
|
25
src/components/dreamarchive/VideoPlayer.tsx
Normal file
25
src/components/dreamarchive/VideoPlayer.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {FaVideo} from 'react-icons/fa';
|
||||||
|
|
||||||
|
interface VideoPlayerProps {
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VideoPlayer: React.FC<VideoPlayerProps> = ({color}) => {
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<div className="aspect-video bg-gray-200 dark:bg-gray-700 rounded-lg flex items-center justify-center">
|
||||||
|
<FaVideo className="text-gray-400 dark:text-gray-500" size={32}/>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
className={`w-12 h-12 rounded-full bg-${color}-500/80 flex items-center justify-center cursor-pointer`}>
|
||||||
|
<div
|
||||||
|
className="w-0 h-0 border-t-8 border-b-8 border-l-12 border-transparent border-l-white ml-1"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoPlayer;
|
Reference in New Issue
Block a user