diff --git a/lib/types.d.ts b/lib/types.d.ts index 21e4e5a..ca82794 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -116,3 +116,15 @@ export type DevProjects = { links: ProjectLink[]; image: string; }; + +export type OfficerSections = + | 'advisor' + | 'board' + | 'media' + | 'research' + | 'development' + | 'projects' + | 'education' + | 'community' + | 'hackutd' + | 'industry'; diff --git a/package-lock.json b/package-lock.json index 6e34be1..c5e74b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@radix-ui/react-hover-card": "^1.1.7", "@radix-ui/react-popover": "^1.1.7", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.13", "@tanstack/react-query": "^5.85.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -39,7 +40,7 @@ "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.6", "tailwindcss": "^3.4.5", - "typescript": "^5" + "typescript": "5.9.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2269,6 +2270,183 @@ } } }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -7300,10 +7478,11 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 4681383..9bc8f77 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-hover-card": "^1.1.7", "@radix-ui/react-popover": "^1.1.7", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.13", "@tanstack/react-query": "^5.85.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -40,6 +41,6 @@ "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.6", "tailwindcss": "^3.4.5", - "typescript": "^5" + "typescript": "5.9.2" } } diff --git a/public/assets/divisions/board.png b/public/assets/divisions/board.png new file mode 100644 index 0000000..532f3fd Binary files /dev/null and b/public/assets/divisions/board.png differ diff --git a/public/assets/divisions/community.png b/public/assets/divisions/community.png new file mode 100644 index 0000000..eadbb7b Binary files /dev/null and b/public/assets/divisions/community.png differ diff --git a/public/assets/divisions/development.png b/public/assets/divisions/development.png new file mode 100644 index 0000000..957dd55 Binary files /dev/null and b/public/assets/divisions/development.png differ diff --git a/public/assets/divisions/education.png b/public/assets/divisions/education.png new file mode 100644 index 0000000..0f18c16 Binary files /dev/null and b/public/assets/divisions/education.png differ diff --git a/public/assets/divisions/hackutd.png b/public/assets/divisions/hackutd.png new file mode 100644 index 0000000..c829a3d Binary files /dev/null and b/public/assets/divisions/hackutd.png differ diff --git a/public/assets/divisions/industry.png b/public/assets/divisions/industry.png new file mode 100644 index 0000000..8896500 Binary files /dev/null and b/public/assets/divisions/industry.png differ diff --git a/public/assets/divisions/media.png b/public/assets/divisions/media.png new file mode 100644 index 0000000..e72f1fd Binary files /dev/null and b/public/assets/divisions/media.png differ diff --git a/public/assets/divisions/projects.png b/public/assets/divisions/projects.png new file mode 100644 index 0000000..13c8a53 Binary files /dev/null and b/public/assets/divisions/projects.png differ diff --git a/public/assets/divisions/research.png b/public/assets/divisions/research.png new file mode 100644 index 0000000..b834f0d Binary files /dev/null and b/public/assets/divisions/research.png differ diff --git a/public/assets/officer-peechi.png b/public/assets/officer-peechi.png new file mode 100644 index 0000000..010b676 Binary files /dev/null and b/public/assets/officer-peechi.png differ diff --git a/src/app/officers/page.tsx b/src/app/officers/page.tsx index 9a8e238..0909974 100644 --- a/src/app/officers/page.tsx +++ b/src/app/officers/page.tsx @@ -1,49 +1,29 @@ -import OfficerGrid from '@/components/Officers/OfficerGrid'; import OfficerHeader from '@/components/Officers/OfficerHeader'; -import Image from 'next/image'; +import { divisionOfficerMap } from '../../../config/officers.config'; +import OfficerClient from '@/components/Officers/OfficerClient'; +import { divisions, allDivisions, titleMap } from '@/components/Officers/constants'; -function Apply() { +function Officers() { return ( -
-
-

- 0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 - 0 1 0 0 1 0 0 1 1 1 1 0 1 0 0 1 0 1 0 0 1 0 0 0 1 0 1 0 1 0 0 0 0 1 1 0 1 0 1 0 1 0 0 0 1 - 0 1 0 0 1 1 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 0 0 - 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 1 0 0 1 0 0 1 1 1 1 0 1 0 0 1 0 1 0 0 1 0 0 0 1 0 1 0 1 0 0 - 0 0 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1 1 -

-

- 0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 - 0 1 0 0 1 0 0 1 1 1 1 0 1 0 0 1 0 1 0 0 1 0 0 0 1 0 1 0 1 0 0 0 0 1 1 0 1 0 1 0 1 0 0 0 1 - 0 1 0 0 1 1 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 0 0 - 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 1 0 0 1 0 0 1 1 1 1 0 1 0 0 1 0 1 0 0 1 0 0 0 1 0 1 0 1 0 0 - 0 0 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1 1 -

-

- 0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 - 0 1 0 0 1 0 0 1 1 1 1 0 1 0 0 1 0 1 0 0 1 0 0 0 1 0 1 0 1 0 0 0 0 1 1 0 1 0 1 0 1 0 0 0 1 - 0 1 0 0 1 1 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 0 0 - 0 0 1 0 1 0 0 0 0 0 1 0 1 0 0 1 0 0 1 0 0 1 1 1 1 0 1 0 0 1 0 1 0 0 1 0 0 0 1 0 1 0 1 0 0 - 0 0 1 1 0 1 0 1 0 1 0 0 0 1 0 1 0 0 1 1 -

-
-
- Division +
+
+ +
+ + +
+
+ +
+
- - - - - - - - - - -
); } -export default Apply; +export default Officers; diff --git a/src/components/Officers/DivisionFilter.tsx b/src/components/Officers/DivisionFilter.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/Officers/DivisionIcon.tsx b/src/components/Officers/DivisionIcon.tsx new file mode 100644 index 0000000..3bfe313 --- /dev/null +++ b/src/components/Officers/DivisionIcon.tsx @@ -0,0 +1,28 @@ +import Image from 'next/image'; +import { OfficerSections } from '../../../lib/types'; + +interface DivisionIconProps { + divisionKey: OfficerSections | 'all'; + label: string; + size?: 'sm' | 'md'; + className?: string; +} + +const DivisionIcon = ({ divisionKey, label, size = 'sm', className = '' }: DivisionIconProps) => { + if (divisionKey === 'all' || divisionKey === 'advisor') { + return null; + } + + const dimensions = size === 'sm' ? { width: 16, height: 16 } : { width: 24, height: 24 }; + + return ( + {`${label} + ); +}; + +export default DivisionIcon; diff --git a/src/components/Officers/OfficerCard.tsx b/src/components/Officers/OfficerCard.tsx new file mode 100644 index 0000000..bf08c9f --- /dev/null +++ b/src/components/Officers/OfficerCard.tsx @@ -0,0 +1,31 @@ +import { type Officer } from '../../../config/officers.config'; +import { OfficerImageWithFallback } from './OfficerImageWithFallback'; + +type OfficerCardProps = { + officer: Officer & { division?: string }; +}; + +export const OfficerCard = ({ officer }: OfficerCardProps) => ( +
+
+
+
+ +
+
+

+ {officer.name} +

+

+ {officer.position} +

+
+
+
+
+); diff --git a/src/components/Officers/OfficerClient.tsx b/src/components/Officers/OfficerClient.tsx new file mode 100644 index 0000000..c00b683 --- /dev/null +++ b/src/components/Officers/OfficerClient.tsx @@ -0,0 +1,176 @@ +'use client'; + +import { useMemo, useState } from 'react'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Input } from '@/components/ui/input'; +import { OfficerSections } from '../../../lib/types'; +import { type Officer } from '../../../config/officers.config'; +import { type DivisionInfo } from './constants'; +import DivisionIcon from './DivisionIcon'; +import { OfficerCard } from './OfficerCard'; + +type OfficerClientProps = { + divisions: DivisionInfo[]; + allDivisions: DivisionInfo[]; + titleMap: Record; + divisionOfficerMap: Record; +}; + +export default function OfficerClient({ + divisions, + allDivisions, + titleMap, + divisionOfficerMap, +}: OfficerClientProps) { + const [searchQuery, setSearchQuery] = useState(''); + + const filteredDivisionOfficerMap = useMemo(() => { + const query = searchQuery.toLowerCase().trim(); + + if (!query) { + return divisionOfficerMap; + } + + return Object.fromEntries( + Object.entries(divisionOfficerMap).map(([divisionKey, officers]) => [ + divisionKey, + officers.filter((officer: Officer) => officer.name.toLowerCase().includes(query)), + ]), + ); + }, [divisionOfficerMap, searchQuery]); + + const groupedOfficersForAll = useMemo(() => { + return divisions + .map((division) => { + const divisionKey = division.key as OfficerSections; + const officers = filteredDivisionOfficerMap[divisionKey]; + + return { + division, + officers: officers, + }; + }) + .filter((group) => group.officers.length > 0); + }, [divisions, filteredDivisionOfficerMap]); + + const totalOfficersForAll = useMemo(() => { + return groupedOfficersForAll.reduce((sum, group) => sum + group.officers.length, 0); + }, [groupedOfficersForAll]); + + return ( + <> +
+ setSearchQuery(e.target.value)} + className="h-12 border-gray-600/50 bg-gray-800/40 text-white backdrop-blur-sm transition-colors duration-200 placeholder:text-gray-400 focus:border-blue-500/70 focus:ring-2 focus:ring-blue-500/20" + /> +
+ + +
+
+ + {allDivisions.map((division: DivisionInfo) => ( + + + {division.label} + + ))} + +
+
+ + {allDivisions.map((division: DivisionInfo) => { + const isAllTab = division.key === 'all'; + + if (isAllTab) { + return ( + + {totalOfficersForAll === 0 ? ( +
+
+ No officers found matching your search criteria. +
+
+ ) : ( +
+ {groupedOfficersForAll.map((group) => ( +
+
+
+ {titleMap[group.division.key as OfficerSections]} +
+
+
+ +
+ {group.officers.map((officer: Officer) => ( + + ))} +
+ + {group.division.key !== 'advisor' && ( +
+ {group.officers.length} officer{group.officers.length !== 1 ? 's' : ''}{' '} + in {group.division.label} +
+ )} +
+ ))} +
+ )} + + {totalOfficersForAll > 0 && ( +
+ {totalOfficersForAll} officer{totalOfficersForAll !== 1 ? 's' : ''} total +
+ )} +
+ ); + } else { + const officers = filteredDivisionOfficerMap[division.key as OfficerSections]; + return ( + +
+
{titleMap[division.key]}
+
+
+ + {officers?.length === 0 ? ( +
+
+ No officers found matching your search criteria. +
+
+ ) : ( +
+ {officers?.map((officer: Officer) => ( + + ))} +
+ )} + + {division.key !== 'advisor' && ( +
+ {officers?.length || 0} officer{officers?.length !== 1 ? 's' : ''} in{' '} + {division.label} +
+ )} +
+ ); + } + })} +
+ + ); +} diff --git a/src/components/Officers/OfficerGrid.tsx b/src/components/Officers/OfficerGrid.tsx deleted file mode 100644 index 7966d3e..0000000 --- a/src/components/Officers/OfficerGrid.tsx +++ /dev/null @@ -1,141 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import { type ReactNode } from 'react'; -import { useState } from 'react'; -import { divisionOfficerMap } from '../../../config/officers.config'; - -type Layout = - | 'advisor' - | 'board' - | 'media' - | 'research' - | 'development' - | 'projects' - | 'education' - | 'community' - | 'hackutd' - | 'industry'; - -type GridProps = { - type: Layout; -}; - -type Officer = { - name: string; - position: string; - image: string; -}; - -type PillProps = { - officer: Officer; -}; - -const titleMap: Record = { - advisor:

the advisor

, - board:

the board

, - media: ACM Media, - research: ( - ACM Research - ), - development: ( - ACM Development - ), - projects: ( - ACM Projects - ), - education: ( - ACM Education - ), - community: ( - ACM Community - ), - hackutd: HackUTD, - industry: ( - ACM Industry - ), -}; - -interface OfficerImageWithFallbackProps { - src: string; - fallbackSrc: string; - alt: string; - className: string; - style: React.CSSProperties; - isJCole: boolean; -} - -const OfficerImageWithFallback = (props: OfficerImageWithFallbackProps) => { - const { src, fallbackSrc, alt, isJCole, ...rest } = props; - const [imgSrc, setImgSrc] = useState(src); - - return ( - {alt} { - setImgSrc(fallbackSrc); - }} - width={isJCole ? 110 : 80} - height={isJCole ? 110 : 80} - sizes="(max-width: 768px) 80px, 110px" - /> - ); -}; - -const OfficerGrid = (props: GridProps) => { - const officers = divisionOfficerMap[props.type]; - return ( -
-
{titleMap[props.type]}
-
- {officers.map((officer) => ( - - ))} -
-
- ); -}; - -const OfficerPill = ({ officer }: PillProps) => ( -
-
- -
-
-

{officer.name}

-

{officer.position}

-
-
-); - -export default OfficerGrid; diff --git a/src/components/Officers/OfficerHeader.tsx b/src/components/Officers/OfficerHeader.tsx index e74acd9..de68707 100644 --- a/src/components/Officers/OfficerHeader.tsx +++ b/src/components/Officers/OfficerHeader.tsx @@ -2,15 +2,36 @@ import Image from 'next/image'; const OfficerHeader = () => { return ( -
-
-

meet the team

-

- 8 divisions. One goal. -
here are the students keeping ACM in motion. -

+
+
+
+
+
+

+ meet the team +

+
+ +

+ 8 divisions. One goal. +
+ here are the students keeping ACM in motion. +

+
+
+
+
+ Peechi +
+
-
); }; diff --git a/src/components/Officers/OfficerImageWithFallback.tsx b/src/components/Officers/OfficerImageWithFallback.tsx new file mode 100644 index 0000000..a1984e3 --- /dev/null +++ b/src/components/Officers/OfficerImageWithFallback.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { useState } from 'react'; +import Image from 'next/image'; + +type OfficerImageWithFallbackProps = { + src: string; + fallbackSrc: string; + alt: string; + className: string; +}; + +export const OfficerImageWithFallback = (props: OfficerImageWithFallbackProps) => { + const { src, fallbackSrc, alt, className } = props; + const [imgSrc, setImgSrc] = useState(src); + + return ( + {alt} { + setImgSrc(fallbackSrc); + }} + height={80} + width={80} + className={className} + /> + ); +}; diff --git a/src/components/Officers/constants.tsx b/src/components/Officers/constants.tsx new file mode 100644 index 0000000..55168b2 --- /dev/null +++ b/src/components/Officers/constants.tsx @@ -0,0 +1,64 @@ +import Image from 'next/image'; +import { OfficerSections } from '../../../lib/types'; +import DivisionIcon from './DivisionIcon'; + +export type DivisionInfo = { key: OfficerSections | 'all'; label: string }; + +export const divisions: DivisionInfo[] = [ + { key: 'board', label: 'Board' }, + { key: 'media', label: 'Media' }, + { key: 'research', label: 'Research' }, + { key: 'development', label: 'Development' }, + { key: 'projects', label: 'Projects' }, + { key: 'education', label: 'Education' }, + { key: 'community', label: 'Community' }, + { key: 'hackutd', label: 'HackUTD' }, + { key: 'industry', label: 'Industry' }, + { key: 'advisor', label: 'Advisor' }, +]; + +export const allDivisions: DivisionInfo[] = [ + { key: 'all' as OfficerSections, label: 'All' }, + ...divisions, +]; + +export const titleMap: Record = { + all: <>, + advisor:

the advisor

, + board:

the board

, + media: ACM Media, + research: ( + ACM Research + ), + development: ( + ACM Development + ), + projects: ( + ACM Projects + ), + education: ( + ACM Education + ), + community: ( + ACM Community + ), + hackutd: HackUTD, + industry: ( + ACM Industry + ), +}; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..cabfbfc --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,76 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..69b64fb --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,22 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Input = React.forwardRef>( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..3c920d8 --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,58 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent }