Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 31 additions & 83 deletions app/installation/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '@heroicons/react/24/outline';
import Image from 'next/image';
import { useState } from 'react';
import CircularButton from '../ui/components/circular-button';

import Data4HealthCoder from './components/Data4HealthCoder';
import Data4HealthNonCoder from './components/Data4HealthNonCoder';
Expand Down Expand Up @@ -41,54 +42,7 @@ interface SubButtonConfig {
}


function CircularButton({ icon, label, onClick, isImage = false, isActive = false }: CircularButtonProps) {
const Icon = icon as React.ElementType; // For when icon is a component
return (
<div className="flex flex-col items-center space-y-2 group">
<div
role="button"
tabIndex={0}
onClick={onClick}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
}}
className={`cursor-pointer transform transition-all duration-300 ease-in-out hover:scale-110 hover:shadow-lg rounded-full p-4 w-24 h-24 flex items-center justify-center
${isActive
? 'bg-purple-100 shadow-lg scale-105 rotate-3'
: 'hover:bg-purple-50 hover:rotate-3'}`}
>
{isImage ? (
<Image
src={icon as string}
alt={label}
width={80}
height={80}
className={`text-current transform transition-transform duration-300
${isActive ? '-rotate-3' : 'group-hover:-rotate-3'}`}
/>
) : (
// allow passing a simple string (emoji) as icon
typeof icon === 'string' ? (
<span className={`text-2xl transition-all duration-300 ${isActive ? 'text-purple-700' : 'text-dark-purple group-hover:text-purple-700'}`}>
{icon}
</span>
) : (
<Icon className={`h-12 w-12 transition-all duration-300
${isActive ? 'text-purple-700' : 'text-dark-purple group-hover:text-purple-700'}`}
/>
)
)}
</div>
<span className={`text-sm font-medium transition-all duration-300
${isActive ? 'text-purple-700' : 'group-hover:text-purple-700'}`}>
{label}
</span>
</div>
);
}
// CircularButton is now a shared component in app/ui/components/circular-button.tsx

interface SubButtonsProps {
section: Section;
Expand Down Expand Up @@ -184,6 +138,14 @@ function InstallationContent({ section, type }: InstallationContentProps) {
export default function Page() {
const [activeSection, setActiveSection] = useState<Section | null>(null);
const [activeInstallType, setActiveInstallType] = useState<'coder' | 'noncoder' | null>(null);
// Toggle buttons by editing this map in code. Set true to disable, false to enable.
const disabledMap: Record<Section, boolean> = {
data4health: false,
clim4health: false,
land4health: false,
socio4health: false,
cube4health: false,
};

const handleToolkitClick = (section: Section) => {
if (section === activeSection) {
Expand All @@ -207,41 +169,27 @@ export default function Page() {
</p>

<div className="mb-8 mt-6 flex flex-wrap justify-center gap-8">
<CircularButton
icon={D4H_LOGO}
label="data4Health"
onClick={() => handleToolkitClick('data4health')}
isImage={true}
isActive={activeSection === 'data4health'}
/>
<CircularButton
icon={C4H_LOGO}
label="clim4health"
onClick={() => handleToolkitClick('clim4health')}
isImage={true}
isActive={activeSection === 'clim4health'}
/>
<CircularButton
icon={L4H_LOGO}
label="land4health"
onClick={() => handleToolkitClick('land4health')}
isImage={true}
isActive={activeSection === 'land4health'}
/>
<CircularButton
icon={S4H_LOGO}
label="socio4health"
onClick={() => handleToolkitClick('socio4health')}
isImage={true}
isActive={activeSection === 'socio4health'}
/>
<CircularButton
icon={CU4H_LOGO}
label="cube4health"
onClick={() => handleToolkitClick('cube4health')}
isImage={true}
isActive={false}
/>
{[
{ key: 'data4health', logo: D4H_LOGO, label: 'data4Health' },
{ key: 'clim4health', logo: C4H_LOGO, label: 'clim4health' },
{ key: 'land4health', logo: L4H_LOGO, label: 'land4health' },
{ key: 'socio4health', logo: S4H_LOGO, label: 'socio4health' },
{ key: 'cube4health', logo: CU4H_LOGO, label: 'cube4health' },
].map((tk) => {
const section = tk.key as Section;
return (
<div key={tk.key} className="flex flex-col items-center">
<CircularButton
icon={tk.logo}
label={tk.label}
onClick={() => handleToolkitClick(section)}
isImage={true}
isActive={activeSection === section}
disabled={disabledMap[section]}
/>
</div>
);
})}
</div>

{activeSection && (
Expand Down
170 changes: 170 additions & 0 deletions app/trainings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
'use client';

import React from 'react';
import Image from 'next/image';
import { useState } from 'react';
import CircularButton from '../ui/components/circular-button';
import DownloadButton from '../ui/components/download-button';

const D4H_LOGO = '/cards/toolkits/data4health.svg';
const C4H_LOGO = '/cards/toolkits/clim4health.svg';
const L4H_LOGO = '/cards/toolkits/land4health.svg';
const S4H_LOGO = '/cards/toolkits/socio4health.svg';
const CU4H_LOGO = '/cards/toolkits/cube4health.svg';


type Section = 'data4health' | 'clim4health' | 'land4health' | 'socio4health' | 'cube4health';

interface CircularButtonProps {
icon: React.ElementType | string;
label: string;
onClick: () => void;
isImage?: boolean;
isActive?: boolean;
size?: 'sm' | 'md';
}


function SubButtons({ section }: { section: Section }) {
const resources: Record<Section, { pdf?: string; related?: Array<{ label: string; href: string; isFile?: boolean; useButton?: boolean }> }> = {
data4health: {
pdf: '/training/data4health-training.pdf',
related: [
//{ label: 'Quickstart (download)', href: '/cards/toolkits/data4health-quickstart.zip', isFile: true },
//{ label: 'Online docs', href: 'https://example.org/data4health' },
],
},
clim4health: {
pdf: '/training/clim4health-training.pdf',
related: [
{ label: 'Clim4Health training files', href: '/training/clim4health_for_website.zip', isFile: true, useButton: true },
],
},
land4health: {
pdf: '/cards/toolkits/land4health-installation.pdf',
related: [
{ label: 'Land4Health dataset', href: '/cards/toolkits/land4health-dataset.zip', isFile: true },
{ label: 'Documentation', href: 'https://example.org/land4health' },
],
},
socio4health: {
pdf: '/cards/toolkits/socio4health-installation.pdf',
related: [
{ label: 'Socio4Health notes (download)', href: '/cards/toolkits/socio4health-notes.pdf', isFile: true },
{ label: 'Website', href: 'https://example.org/socio4health' },
],
},
cube4health: {
pdf: '/training/cube4health-training.pdf',
related: [
//{ label: 'Cube4Health examples', href: '/cards/toolkits/cube4health-examples.zip', isFile: true },
//{ label: 'Repo', href: 'https://example.org/cube4health' },
],
},
};

const entry = resources[section];

return (
<article className="prose mx-auto mt-8 w-full max-w-4xl">

{entry?.pdf ? (
<div className="mt-6 border rounded overflow-hidden" style={{ height: 720 }}>
{/* The iframe/embed will show the PDF. If the PDF isn't present, users
can use the link below to open/download it. */}
<iframe
src={entry.pdf}
title={`${section} installation guide`}
className="w-full h-full"
style={{ border: 'none' }}
/>
</div>
) : (
<p className="mt-4 text-sm text-gray-500">No PDF guide available for this toolkit.</p>
)}

{entry.related && entry.related.length > 0 && (
<div className="mt-6">
<h3 className="text-lg font-medium">References</h3>
<div className="mt-3 space-y-3">
{entry.related.map((r, i) => (
<div key={i} className="my-0">
{r.useButton && r.isFile ? (
<DownloadButton href={r.href} label={r.label} />
) : (
<a
href={r.href}
target="_blank"
rel="noopener noreferrer"
className="text-purple-700 hover:underline block"
{...(r.isFile ? { download: '' } : {})}
>
{r.label}
</a>
)}
</div>
))}
</div>
</div>
)}
</article>
);
}


export default function Page() {
const [activeSection, setActiveSection] = useState<Section | null>(null);
// Toggle buttons by editing this map in code. Set true to disable, false to enable.
const disabledMap: Record<Section, boolean> = {
data4health: false,
clim4health: true,
land4health: true,
socio4health: true,
cube4health: true,
};

const handleToolkitClick = (section: Section) => {
if (section === activeSection) {
setActiveSection(null);
} else {
setActiveSection(section);
}
};

return (
<div className="container mx-auto px-4">
<h1 className="mb-2 mt-6 text-3xl font-semibold md:mt-0">Trainings</h1>
<p className="text-justify text-l text-gray-500">
This section contains the workshop development support materials.
</p>

<div className="mb-8 mt-6 flex flex-wrap justify-center gap-8">
{[
{ key: 'data4health', logo: D4H_LOGO, label: 'data4Health' },
{ key: 'clim4health', logo: C4H_LOGO, label: 'clim4health' },
{ key: 'land4health', logo: L4H_LOGO, label: 'land4health' },
{ key: 'socio4health', logo: S4H_LOGO, label: 'socio4health' },
{ key: 'cube4health', logo: CU4H_LOGO, label: 'cube4health' },
].map((tk) => {
const section = tk.key as Section;
return (
<div key={tk.key} className="flex flex-col items-center">
<CircularButton
icon={tk.logo}
label={tk.label}
onClick={() => handleToolkitClick(section)}
isImage={true}
isActive={activeSection === section}
disabled={disabledMap[section]}
/>
</div>
);
})}
</div>

{activeSection && (
<SubButtons section={activeSection} />
)}
</div>
);
}
65 changes: 65 additions & 0 deletions app/ui/components/circular-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import Image from 'next/image';

export interface CircularButtonProps {
icon: React.ElementType | string;
label: string;
onClick: () => void;
isImage?: boolean;
isActive?: boolean;
disabled?: boolean;
size?: 'sm' | 'md';
}

export default function CircularButton({ icon, label, onClick, isImage = false, isActive = false, disabled = false }: CircularButtonProps) {
const Icon = icon as React.ElementType;
// stronger fade when disabled and remove hover effects
const containerClasses = `transform transition-all duration-300 ease-in-out rounded-full p-4 w-24 h-24 flex items-center justify-center ${
disabled ? 'opacity-30 cursor-not-allowed' : 'cursor-pointer hover:scale-110 hover:shadow-lg'
}`;

const activeBg = isActive ? 'bg-purple-100 shadow-lg scale-105 rotate-3' : (disabled ? '' : 'hover:bg-purple-50 hover:rotate-3');

return (
<div className="flex flex-col items-center space-y-2 group">
<div
role="button"
tabIndex={disabled ? -1 : 0}
aria-disabled={disabled}
onClick={() => {
if (!disabled) onClick();
}}
onKeyDown={(e) => {
if (disabled) return;
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
}}
className={`${containerClasses} ${activeBg}`}
>
{isImage ? (
<Image
src={icon as string}
alt={label}
width={80}
height={80}
className={`text-current transform transition-transform duration-300 ${disabled ? 'filter grayscale' : ''} ${isActive ? '-rotate-3' : (disabled ? '' : 'group-hover:-rotate-3')}`}
/>
) : (
typeof icon === 'string' ? (
<span className={`text-2xl transition-all duration-300 ${isActive ? 'text-purple-700' : 'text-dark-purple'} ${disabled ? 'text-gray-400' : 'group-hover:text-purple-700'}`}>
{icon}
</span>
) : (
<Icon className={`h-12 w-12 transition-all duration-300 ${isActive ? 'text-purple-700' : 'text-dark-purple'} ${disabled ? 'filter grayscale text-gray-400' : 'group-hover:text-purple-700'}`}
/>
)
)}
</div>
<span className={`text-sm font-medium transition-all duration-300 ${isActive ? 'text-purple-700' : (disabled ? 'text-gray-400' : 'group-hover:text-purple-700')}`}>
{label}
</span>
</div>
);
}
Loading