|
| 1 | +import React, { useState } from 'react'; |
| 2 | +import Image from 'next/image'; |
| 3 | +// import { Github, Twitter, Linkedin } from 'lucide-react'; |
| 4 | + |
| 5 | +// Define the types for the `ambassador` prop |
| 6 | +interface Contribution { |
| 7 | + title: string; |
| 8 | + date?: { month: string; year: number }; |
| 9 | + link: string; |
| 10 | + type: string; |
| 11 | +} |
| 12 | + |
| 13 | +interface Ambassador { |
| 14 | + img?: string; |
| 15 | + name?: string; |
| 16 | + title?: string; |
| 17 | + bio?: string; |
| 18 | + company?: string; |
| 19 | + country?: string; |
| 20 | + github?: string; |
| 21 | + twitter?: string; |
| 22 | + mastodon?: string; |
| 23 | + linkedin?: string; |
| 24 | + contributions?: Contribution[]; |
| 25 | +} |
| 26 | + |
| 27 | +interface AmbassadorCardProps { |
| 28 | + ambassador: Ambassador; |
| 29 | +} |
| 30 | + |
| 31 | +type SocialPlatform = 'github' | 'twitter' | 'mastodon' | 'linkedin'; |
| 32 | + |
| 33 | +// Utility function to generate full URLs for social media |
| 34 | +const getSocialMediaUrl = ( |
| 35 | + platform: SocialPlatform, |
| 36 | + username: string | undefined, |
| 37 | +) => { |
| 38 | + const baseUrls: Record<SocialPlatform, string> = { |
| 39 | + github: 'https://github.com/', |
| 40 | + twitter: 'https://twitter.com/', |
| 41 | + mastodon: 'https://mastodon.social/', |
| 42 | + linkedin: 'https://www.linkedin.com/in/', |
| 43 | + }; |
| 44 | + return username ? baseUrls[platform] + username : undefined; |
| 45 | +}; |
| 46 | + |
| 47 | +// Social media icon component with proper typing |
| 48 | +const SocialIcon: React.FC<{ platform: SocialPlatform }> = ({ platform }) => { |
| 49 | + const icons: Record<SocialPlatform, JSX.Element> = { |
| 50 | + github: ( |
| 51 | + <svg |
| 52 | + className='w-7 h-7 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100' |
| 53 | + viewBox='0 0 24 24' |
| 54 | + fill='currentColor' |
| 55 | + xmlns='http://www.w3.org/2000/svg' |
| 56 | + > |
| 57 | + <path d='M12 0C5.373 0 0 5.373 0 12c0 5.304 3.438 9.8 8.207 11.387.6.113.82-.26.82-.577 0-.287-.01-1.049-.016-2.06-3.338.726-4.043-1.609-4.043-1.609-.546-1.387-1.333-1.756-1.333-1.756-1.09-.746.084-.73.084-.73 1.205.084 1.84 1.238 1.84 1.238 1.07 1.834 2.81 1.303 3.492.997.108-.774.42-1.303.763-1.603-2.665-.304-5.467-1.332-5.467-5.93 0-1.31.469-2.381 1.236-3.22-.124-.303-.536-1.523.117-3.176 0 0 1.008-.322 3.302 1.23A11.503 11.503 0 0 1 12 6.802c1.018.004 2.046.137 3.003.403 2.293-1.552 3.3-1.23 3.3-1.23.654 1.653.242 2.873.118 3.176.77.839 1.236 1.91 1.236 3.22 0 4.61-2.804 5.624-5.474 5.921.43.37.816 1.102.816 2.221 0 1.606-.014 2.901-.014 3.293 0 .32.216.694.824.576C20.566 21.796 24 17.3 24 12 24 5.373 18.627 0 12 0z' /> |
| 58 | + </svg> |
| 59 | + ), |
| 60 | + twitter: ( |
| 61 | + <svg |
| 62 | + className='w-7 h-7 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100' |
| 63 | + viewBox='0 0 24 24' |
| 64 | + fill='currentColor' |
| 65 | + xmlns='http://www.w3.org/2000/svg' |
| 66 | + > |
| 67 | + <path d='M23.954 4.569c-.885.389-1.83.654-2.825.775a4.932 4.932 0 0 0 2.163-2.723 9.85 9.85 0 0 1-3.127 1.195 4.916 4.916 0 0 0-8.374 4.482A13.936 13.936 0 0 1 1.64 3.161 4.916 4.916 0 0 0 3.195 9.86a4.897 4.897 0 0 1-2.229-.616v.061a4.919 4.919 0 0 0 3.946 4.827 4.897 4.897 0 0 1-2.224.085 4.923 4.923 0 0 0 4.604 3.42A9.869 9.869 0 0 1 .977 19.569a13.94 13.94 0 0 0 7.548 2.211c9.056 0 14.012-7.497 14.012-13.986 0-.213-.005-.425-.015-.636A9.936 9.936 0 0 0 24 4.59a9.94 9.94 0 0 1-2.046.561z' /> |
| 68 | + </svg> |
| 69 | + ), |
| 70 | + linkedin: ( |
| 71 | + <svg |
| 72 | + className='w-7 h-7 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100' |
| 73 | + viewBox='0 0 24 24' |
| 74 | + fill='currentColor' |
| 75 | + xmlns='http://www.w3.org/2000/svg' |
| 76 | + > |
| 77 | + <path d='M22.225 0H1.771C.792 0 0 .775 0 1.729V22.27c0 .955.793 1.729 1.771 1.729h20.451c.978 0 1.778-.775 1.778-1.729V1.729C24 .774 23.203 0 22.225 0zM7.113 20.452H3.56V8.997h3.552v11.455zm-1.78-13.044a2.073 2.073 0 1 1 0-4.145 2.073 2.073 0 0 1 0 4.145zm15.34 13.044h-3.552v-5.697c0-1.357-.027-3.1-1.892-3.1-1.892 0-2.182 1.48-2.182 3.003v5.794H10.84V8.997h3.412v1.568h.049c.474-.896 1.637-1.84 3.37-1.84 3.604 0 4.268 2.371 4.268 5.451v6.276h-.002z' /> |
| 78 | + </svg> |
| 79 | + ), |
| 80 | + mastodon: ( |
| 81 | + <svg |
| 82 | + className='w-7 h-7 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100' |
| 83 | + viewBox='0 0 24 24' |
| 84 | + fill='currentColor' |
| 85 | + xmlns='http://www.w3.org/2000/svg' |
| 86 | + > |
| 87 | + <path d='M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z' /> |
| 88 | + </svg> |
| 89 | + ), |
| 90 | + }; |
| 91 | + return icons[platform]; |
| 92 | +}; |
| 93 | + |
| 94 | +const AmbassadorCard: React.FC<AmbassadorCardProps> = ({ ambassador }) => { |
| 95 | + const [showContributions, setShowContributions] = useState(false); |
| 96 | + const [imgSrc, setImgSrc] = useState( |
| 97 | + ambassador.img || '/api/placeholder/400/320', |
| 98 | + ); |
| 99 | + |
| 100 | + const { |
| 101 | + name = 'Ambassador', |
| 102 | + title, |
| 103 | + bio, |
| 104 | + company, |
| 105 | + country, |
| 106 | + contributions = [], |
| 107 | + } = ambassador; |
| 108 | + |
| 109 | + // Available social platforms with proper typing |
| 110 | + const socialPlatforms: SocialPlatform[] = [ |
| 111 | + 'github', |
| 112 | + 'twitter', |
| 113 | + 'mastodon', |
| 114 | + 'linkedin', |
| 115 | + ]; |
| 116 | + |
| 117 | + return ( |
| 118 | + <div className='relative max-w-sm md:max-w-md lg:max-w-lg mx-auto bg-white dark:bg-gray-800 shadow-lg rounded-lg overflow-hidden my-4 transition-all duration-300 h-fit'> |
| 119 | + {/* Black Borders */} |
| 120 | + <div className='absolute top-0 right-0 w-1 h-20 bg-black dark:bg-gray-400'></div> |
| 121 | + <div className='absolute bottom-100 right-0 w-20 h-1 bg-black dark:bg-gray-400'></div> |
| 122 | + <div className='absolute bottom-0 left-0 w-1 h-20 bg-black dark:bg-gray-400'></div> |
| 123 | + <div className='absolute bottom-0 left-0 w-20 h-1 bg-black dark:bg-gray-400'></div> |
| 124 | + |
| 125 | + <Image |
| 126 | + className='w-full object-cover p-5 rounded-3xl' |
| 127 | + src={imgSrc} |
| 128 | + alt={`${name} profile`} |
| 129 | + width={400} |
| 130 | + height={320} |
| 131 | + onError={() => setImgSrc(`/img/ambassadors/${name}.jpg`)} |
| 132 | + /> |
| 133 | + |
| 134 | + <div className='p-6'> |
| 135 | + <h3 className='text-xl font-semibold mb-2 text-gray-900 dark:text-white'> |
| 136 | + {name} |
| 137 | + </h3> |
| 138 | + {title && ( |
| 139 | + <p className='text-gray-500 dark:text-slate-100 mb-1'>{title}</p> |
| 140 | + )} |
| 141 | + {bio && ( |
| 142 | + <p className='text-gray-700 dark:text-slate-100 text-sm mb-4'> |
| 143 | + {bio} |
| 144 | + </p> |
| 145 | + )} |
| 146 | + {(company || country) && ( |
| 147 | + <p className='text-gray-500 dark:text-slate-100 mb-4'> |
| 148 | + {company} |
| 149 | + {company && country && ', '} |
| 150 | + {country} |
| 151 | + </p> |
| 152 | + )} |
| 153 | + |
| 154 | + {/* Social Media Links */} |
| 155 | + <div className='flex justify-center mb-4'> |
| 156 | + {socialPlatforms.map((platform) => { |
| 157 | + const username = ambassador[platform]; |
| 158 | + return username ? ( |
| 159 | + <a |
| 160 | + key={platform} |
| 161 | + href={getSocialMediaUrl(platform, username)} |
| 162 | + target='_blank' |
| 163 | + rel='noopener noreferrer' |
| 164 | + className='transition-colors duration-200 mr-4' |
| 165 | + aria-label={`${name}'s ${platform} profile`} |
| 166 | + > |
| 167 | + <SocialIcon platform={platform} /> |
| 168 | + </a> |
| 169 | + ) : null; |
| 170 | + })} |
| 171 | + </div> |
| 172 | + |
| 173 | + {/* Button to Show/Hide Contributions */} |
| 174 | + {contributions.length > 0 && ( |
| 175 | + <button |
| 176 | + onClick={() => setShowContributions(!showContributions)} |
| 177 | + className={`w-full bg-blue-600 dark:bg-blue-500 hover:bg-blue-700 dark:hover:bg-blue-400 text-white dark:text-slate-100 font-semibold py-2 px-4 rounded transition-all duration-300 transform ${ |
| 178 | + showContributions ? 'rotate' : '' |
| 179 | + }`} |
| 180 | + > |
| 181 | + {showContributions ? 'Hide Details' : 'Show Full Details'} |
| 182 | + </button> |
| 183 | + )} |
| 184 | + |
| 185 | + {/* Contributions Section (Toggled) */} |
| 186 | + {showContributions && contributions.length > 0 && ( |
| 187 | + <div className='mt-4'> |
| 188 | + <h4 className='text-lg font-semibold mb-2 text-gray-900 dark:text-white'> |
| 189 | + Contributions |
| 190 | + </h4> |
| 191 | + <ul className='text-gray-600 dark:text-slate-100 text-sm'> |
| 192 | + {contributions.map((contribution, index) => ( |
| 193 | + <li key={index} className='mb-2'> |
| 194 | + <strong>{contribution.title}</strong> |
| 195 | + {contribution.date && |
| 196 | + ` (${contribution.date.month} ${contribution.date.year})`}{' '} |
| 197 | + - |
| 198 | + <a |
| 199 | + href={contribution.link} |
| 200 | + className='text-blue-600 dark:text-blue-400 ml-1 hover:underline' |
| 201 | + target='_blank' |
| 202 | + rel='noopener noreferrer' |
| 203 | + > |
| 204 | + {contribution.type} |
| 205 | + </a> |
| 206 | + </li> |
| 207 | + ))} |
| 208 | + </ul> |
| 209 | + </div> |
| 210 | + )} |
| 211 | + </div> |
| 212 | + </div> |
| 213 | + ); |
| 214 | +}; |
| 215 | + |
| 216 | +export default AmbassadorCard; |
0 commit comments