diff --git a/assets/pasted-20260213-200505-610d0266.png b/assets/pasted-20260213-200505-610d0266.png
new file mode 100644
index 0000000..645797d
Binary files /dev/null and b/assets/pasted-20260213-200505-610d0266.png differ
diff --git a/frontend/src/colors.ts b/frontend/src/colors.ts
index 9d6920a..7c84fca 100644
--- a/frontend/src/colors.ts
+++ b/frontend/src/colors.ts
@@ -6,7 +6,7 @@ export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-
export const gradientBgViolet = `${gradientBgBase} ${colorBgBase}`
export const gradientBgDark = `${gradientBgBase} from-dark-700 via-dark-900 to-dark-800`;
export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to-yellow-500`
-export const gradientBgExecutive = 'bg-gradient-to-br from-white to-[#091EAA]/[0.07]'
+export const gradientBgExecutive = 'bg-gradient-to-br from-white to-pavitra-blue/[0.07]'
export const colorsBgLight = {
white: 'bg-white text-black',
@@ -15,7 +15,7 @@ export const colorsBgLight = {
success: 'bg-emerald-500 border-emerald-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
danger: 'bg-red-500 border-red-500 text-white',
warning: 'bg-yellow-500 border-yellow-500 text-white',
- info: 'bg-blue-500 border-blue-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
+ info: 'bg-pavitra-blue border-pavitra-blue dark:bg-pavitra-blue dark:border-pavitra-blue text-white',
}
export const colorsText = {
@@ -25,7 +25,7 @@ export const colorsText = {
success: 'text-emerald-500',
danger: 'text-red-500',
warning: 'text-yellow-500',
- info: 'text-blue-500',
+ info: 'text-pavitra-blue',
};
export const colorsOutline = {
@@ -35,7 +35,7 @@ export const colorsOutline = {
success: [colorsText.success, 'border-emerald-500'].join(' '),
danger: [colorsText.danger, 'border-red-500'].join(' '),
warning: [colorsText.warning, 'border-yellow-500'].join(' '),
- info: [colorsText.info, 'border-blue-500'].join(' '),
+ info: [colorsText.info, 'border-pavitra-blue'].join(' '),
};
export const getButtonColor = (
@@ -57,7 +57,7 @@ export const getButtonColor = (
success: 'ring-emerald-300 dark:ring-pavitra-blue',
danger: 'ring-red-300 dark:ring-red-700',
warning: 'ring-yellow-300 dark:ring-yellow-700',
- info: "ring-blue-300 dark:ring-pavitra-blue",
+ info: "ring-pavitra-blue/30 dark:ring-pavitra-blue/50",
},
active: {
white: 'bg-gray-100',
@@ -67,7 +67,7 @@ export const getButtonColor = (
success: 'bg-emerald-700 dark:bg-pavitra-blue',
danger: 'bg-red-700 dark:bg-red-600',
warning: 'bg-yellow-700 dark:bg-yellow-600',
- info: 'bg-blue-700 dark:bg-pavitra-blue',
+ info: 'bg-pavitra-blue/90 dark:bg-pavitra-blue/90',
},
bg: {
white: 'bg-white text-black',
@@ -77,7 +77,7 @@ export const getButtonColor = (
success: 'bg-emerald-600 dark:bg-pavitra-blue text-white',
danger: 'bg-red-600 text-white dark:bg-red-500 ',
warning: 'bg-yellow-600 dark:bg-yellow-500 text-white',
- info: " bg-blue-600 dark:bg-pavitra-blue text-white ",
+ info: " bg-pavitra-blue dark:bg-pavitra-blue text-white ",
},
bgHover: {
white: 'hover:bg-gray-100',
@@ -90,7 +90,7 @@ export const getButtonColor = (
'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600',
warning:
'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600',
- info: "hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80",
+ info: "hover:bg-pavitra-blue/80 hover:border-pavitra-blue/80 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80",
},
borders: {
white: 'border-white',
@@ -100,14 +100,14 @@ export const getButtonColor = (
success: 'border-emerald-600 dark:border-pavitra-blue',
danger: 'border-red-600 dark:border-red-500',
warning: 'border-yellow-600 dark:border-yellow-500',
- info: "border-blue-600 border-blue-600 dark:border-pavitra-blue",
+ info: "border-pavitra-blue dark:border-pavitra-blue",
},
text: {
contrast: 'dark:text-slate-100',
success: 'text-emerald-600 dark:text-pavitra-blue',
danger: 'text-red-600 dark:text-red-500',
warning: 'text-yellow-600 dark:text-yellow-500',
- info: 'text-blue-600 dark:text-pavitra-blue',
+ info: 'text-pavitra-blue dark:text-pavitra-blue',
},
outlineHover: {
contrast:
@@ -117,7 +117,7 @@ export const getButtonColor = (
'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600',
warning:
'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600',
- info: "hover:bg-blue-600 hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue",
+ info: "hover:bg-pavitra-blue hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue",
},
}
diff --git a/frontend/src/components/Directory/MemberCard.tsx b/frontend/src/components/Directory/MemberCard.tsx
new file mode 100644
index 0000000..e2166ad
--- /dev/null
+++ b/frontend/src/components/Directory/MemberCard.tsx
@@ -0,0 +1,138 @@
+import React from 'react';
+import { mdiLinkedin, mdiMapMarkerOutline, mdiEmailOutline, mdiPhoneOutline, mdiMessageTextOutline, mdiCrown } from '@mdi/js';
+import BaseIcon from '../BaseIcon';
+import BaseButton from '../BaseButton';
+import Link from 'next/link';
+import { useAppSelector } from '../../stores/hooks';
+
+type Props = {
+ member: any;
+};
+
+const MemberCard = ({ member }: Props) => {
+ const brandColor = '#091EAA';
+ const { currentUser } = useAppSelector((state) => state.auth);
+
+ const headshot = member.headshot && member.headshot[0] ? member.headshot[0].publicUrl : null;
+ const firstName = member.user?.firstName || '';
+ const lastName = member.user?.lastName || '';
+ const fullName = `${firstName} ${lastName}`.trim() || 'Anonymous Member';
+ const initials = `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase() || '?';
+
+ const memberSinceYear = member.member_since ? new Date(member.member_since).getFullYear() : null;
+
+ const mailtoLink = `mailto:${member.user?.email}?subject=Power Suite Connection – ${currentUser?.firstName || 'A Member'}`;
+
+ return (
+
+
+
+ {/* Avatar */}
+
+ {headshot ? (
+

+ ) : (
+
+ {initials}
+
+ )}
+
+
+
+ {/* Name */}
+
+ {fullName}
+
+
+ {/* Title Badge */}
+
+ {member.professional_title || 'Executive'}
+
+
+ {/* Organization */}
+
+ {member.organization || 'Independent'}
+
+
+
+
+ {/* Pills */}
+
+ {member.industry && (
+
+ {member.industry}
+
+ )}
+ {member.sector && (
+
+ {member.sector === 'nonprofit_501c3' ? 'Nonprofit (501c3)' : member.sector.charAt(0).toUpperCase() + member.sector.slice(1)}
+
+ )}
+
+
+ {/* Location */}
+
+
+ {member.city}{member.city && member.state ? ', ' : ''}{member.state}
+
+
+
+ {/* Action Buttons */}
+
+
+
+ {member.linkedin_url && (
+
+
+
+ )}
+
+ {member.allow_phone_contact && member.phone_number && (
+
+ )}
+
+
+ {/* Footer Badges */}
+
+ {member.founding_member && (
+
+
+ Zeta Proud
+
+ )}
+ {memberSinceYear && (
+
+ Member since {memberSinceYear}
+
+ )}
+
+
+ );
+};
+
+export default MemberCard;
diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index 4325420..3d426e2 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -8,7 +8,7 @@ const menuAside: MenuAsideItem[] = [
label: 'Home',
},
{
- href: '/member_profiles/member_profiles-list',
+ href: '/directory',
label: 'Executive Directory',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
@@ -25,11 +25,6 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiShieldEditOutline,
permissions: 'READ_USERS', // Only show to users who can read users (Admins)
menu: [
- {
- href: '/dashboard',
- icon: icon.mdiChartTimelineVariant,
- label: 'Analytics',
- },
{
href: '/users/users-list',
label: 'Member Accounts',
@@ -69,4 +64,4 @@ const menuAside: MenuAsideItem[] = [
},
]
-export default menuAside
\ No newline at end of file
+export default menuAside
diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx
index c4ccb96..e44b2f7 100644
--- a/frontend/src/pages/dashboard.tsx
+++ b/frontend/src/pages/dashboard.tsx
@@ -1,8 +1,9 @@
import * as icon from '@mdi/js';
import Head from 'next/head'
-import React from 'react'
+import React, { useEffect } from 'react'
import axios from 'axios';
import type { ReactElement } from 'react'
+import { useRouter } from 'next/router';
import LayoutAuthenticated from '../layouts/Authenticated'
import SectionMain from '../components/SectionMain'
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
@@ -16,15 +17,16 @@ import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
+
const Dashboard = () => {
const dispatch = useAppDispatch();
+ const router = useRouter();
const iconsColor = useAppSelector((state) => state.style.iconsColor);
const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const loadingMessage = 'Loading...';
-
const [users, setUsers] = React.useState(loadingMessage);
const [roles, setRoles] = React.useState(loadingMessage);
const [permissions, setPermissions] = React.useState(loadingMessage);
@@ -34,29 +36,31 @@ const Dashboard = () => {
const [email_reminders, setEmail_reminders] = React.useState(loadingMessage);
const [audit_events, setAudit_events] = React.useState(loadingMessage);
-
const [widgetsRole, setWidgetsRole] = React.useState({
role: { value: '', label: '' },
});
const { currentUser } = useAppSelector((state) => state.auth);
const { isFetchingQuery } = useAppSelector((state) => state.openAi);
-
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
-
+
+ // Disable dashboard for non-admin members
+ useEffect(() => {
+ if (currentUser && !hasPermission(currentUser, 'READ_USERS')) {
+ router.push('/directory');
+ }
+ }, [currentUser, router]);
async function loadData() {
const entities = ['users','roles','permissions','member_profiles','expertise_areas','member_import_batches','email_reminders','audit_events',];
const fns = [setUsers,setRoles,setPermissions,setMember_profiles,setExpertise_areas,setMember_import_batches,setEmail_reminders,setAudit_events,];
const requests = entities.map((entity, index) => {
-
if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
return axios.get(`/${entity.toLowerCase()}/count`);
} else {
fns[index](null);
return Promise.resolve({data: {count: null}});
}
-
});
Promise.allSettled(requests).then((results) => {
@@ -73,17 +77,25 @@ const Dashboard = () => {
async function getWidgets(roleId) {
await dispatch(fetchWidgets(roleId));
}
+
React.useEffect(() => {
if (!currentUser) return;
+ if (!hasPermission(currentUser, 'READ_USERS')) return; // Don't load if about to redirect
loadData().then();
setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } });
}, [currentUser]);
React.useEffect(() => {
if (!currentUser || !widgetsRole?.role?.value) return;
+ if (!hasPermission(currentUser, 'READ_USERS')) return;
getWidgets(widgetsRole?.role?.value || '').then();
}, [widgetsRole?.role?.value]);
+ // Return null if redirecting to avoid flash of dashboard
+ if (currentUser && !hasPermission(currentUser, 'READ_USERS')) {
+ return null;
+ }
+
return (
<>
@@ -141,8 +153,6 @@ const Dashboard = () => {
{!!rolesWidgets.length &&
}
-
-
{hasPermission(currentUser, 'READ_USERS') &&
{
w="w-16"
h="h-16"
size={48}
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
path={icon.mdiAccountGroup || icon.mdiTable}
/>
@@ -190,8 +198,6 @@ const Dashboard = () => {
w="w-16"
h="h-16"
size={48}
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
/>
@@ -218,8 +224,6 @@ const Dashboard = () => {
w="w-16"
h="h-16"
size={48}
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
path={icon.mdiShieldAccountOutline || icon.mdiTable}
/>
@@ -227,7 +231,7 @@ const Dashboard = () => {
}
- {hasPermission(currentUser, 'READ_MEMBER_PROFILES') &&
+ {hasPermission(currentUser, 'READ_MEMBER_PROFILES') &&
@@ -246,8 +250,6 @@ const Dashboard = () => {
w="w-16"
h="h-16"
size={48}
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
path={'mdiBadgeAccountOutline' in icon ? icon['mdiBadgeAccountOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
@@ -274,8 +276,6 @@ const Dashboard = () => {
w="w-16"
h="h-16"
size={48}
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
path={'mdiLightbulbOnOutline' in icon ? icon['mdiLightbulbOnOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
@@ -302,8 +302,6 @@ const Dashboard = () => {
w="w-16"
h="h-16"
size={48}
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
path={'mdiFileUploadOutline' in icon ? icon['mdiFileUploadOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
@@ -330,8 +328,6 @@ const Dashboard = () => {
w="w-16"
h="h-16"
size={48}
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
path={'mdiEmailOutline' in icon ? icon['mdiEmailOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
@@ -358,16 +354,12 @@ const Dashboard = () => {
w="w-16"
h="h-16"
size={48}
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
path={'mdiClipboardTextOutline' in icon ? icon['mdiClipboardTextOutline' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
}
-
-
>
@@ -378,4 +370,4 @@ Dashboard.getLayout = function getLayout(page: ReactElement) {
return {page}
}
-export default Dashboard
+export default Dashboard
\ No newline at end of file
diff --git a/frontend/src/pages/directory/[id].tsx b/frontend/src/pages/directory/[id].tsx
new file mode 100644
index 0000000..1354214
--- /dev/null
+++ b/frontend/src/pages/directory/[id].tsx
@@ -0,0 +1,288 @@
+import React, { ReactElement, useEffect, useMemo } from 'react';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import { mdiLinkedin, mdiMapMarkerOutline, mdiEmailOutline, mdiPhoneOutline, mdiMessageTextOutline, mdiArrowLeft, mdiCrown } from '@mdi/js';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import { fetch } from '../../stores/member_profiles/member_profilesSlice';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import { getPageTitle } from '../../config';
+import BaseIcon from '../../components/BaseIcon';
+import BaseButton from '../../components/BaseButton';
+import LoadingSpinner from '../../components/LoadingSpinner';
+
+const MemberProfileDetail = () => {
+ const router = useRouter();
+ const dispatch = useAppDispatch();
+ const { id } = router.query;
+ const { member_profiles, loading } = useAppSelector((state) => state.member_profiles);
+ const { currentUser } = useAppSelector((state) => state.auth);
+
+ useEffect(() => {
+ if (id) {
+ dispatch(fetch({ id }));
+ }
+ }, [dispatch, id]);
+
+ const member = member_profiles;
+
+ const isProfileAvailable = useMemo(() => {
+ if (!member) return false;
+
+ // Always allow the user to see their own profile?
+ // The requirement says "not be accessible for inactive/non-consented profiles".
+ // I will stick to the rule strictly.
+ const isActive = member.profile_status === 'active';
+ const isConsented = member.directory_consent === true;
+ const isReconfirmed = member.confirmation_expires_at ? new Date(member.confirmation_expires_at) > new Date() : false;
+
+ return isActive && isConsented && isReconfirmed;
+ }, [member]);
+
+ if (loading || !member) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ if (!isProfileAvailable) {
+ return (
+
+
+
Profile unavailable
+
This profile is currently private or inactive.
+
router.push('/directory')}
+ className="bg-[#091EAA] border-none rounded-none"
+ />
+
+
+ );
+ }
+
+ const fullName = `${member.user?.firstName || ''} ${member.user?.lastName || ''}`.trim() || 'Anonymous Member';
+ const headshot = member.headshot && member.headshot[0] ? member.headshot[0].publicUrl : null;
+ const initials = `${(member.user?.firstName || '').charAt(0)}${(member.user?.lastName || '').charAt(0)}`.toUpperCase() || '?';
+ const mailtoLink = `mailto:${member.user?.email}?subject=Power Suite Connection – ${currentUser?.firstName || 'A Member'}`;
+
+ return (
+ <>
+
+ {getPageTitle(fullName)}
+
+
+
+
+
+
+
+
+ {/* Header Section */}
+
+
+ {/* Avatar */}
+
+ {headshot ? (
+

+ ) : (
+
+ {initials}
+
+ )}
+
+
+ {/* Basic Info */}
+
+
+
+ {fullName}
+
+ {member.founding_member && (
+
+
+ Zeta Proud
+
+ )}
+
+
+
+ {member.professional_title || 'Executive'}
+
+
+
+ {member.organization}
+
+
+
+
+
+ {member.city}, {member.state}
+
+ {member.member_since && (
+
+ Member Since:
+ {new Date(member.member_since).getFullYear()}
+
+ )}
+
+
+
+ {/* Actions */}
+
+
+
+
+ {member.linkedin_url && (
+
+
+
+ )}
+
+ {member.allow_phone_contact && member.phone_number && (
+ <>
+
+
+
+
+
+
+ >
+ )}
+
+
+
+
+
+ {/* Content Section */}
+
+ {/* Left Column: Bio & Spotlight */}
+
+
+ Professional Bio
+ {member.professional_bio ? (
+
+ ) : (
+ No bio provided.
+ )}
+
+
+ {member.recognition_spotlight && (
+
+ Recognition & Spotlight
+
+
+ )}
+
+
+ {/* Right Column: Expertise & Details */}
+
+
+ Details
+
+
+
Industry
+
{member.industry || 'N/A'}
+
+
+
Sector
+
+ {member.sector === 'nonprofit_501c3' ? 'Nonprofit (501c3)' : member.sector}
+
+
+ {member.zeta_region && (
+
+
Zeta Region
+
{member.zeta_region}
+
+ )}
+ {member.leadership_level && (
+
+
Leadership Level
+
+ {member.leadership_level.replace(/_/g, ' ')}
+
+
+ )}
+
+
+
+ {member.areas_of_expertise && member.areas_of_expertise.length > 0 && (
+
+ Areas of Expertise
+
+ {member.areas_of_expertise.map((area: any) => (
+
+ {area.name}
+
+ ))}
+
+
+ )}
+
+ {member.mentorship_interest && member.mentorship_interest !== 'neither' && (
+
+ Mentorship
+
+
+ Interested as: {member.mentorship_interest}
+
+
+
+ )}
+
+
+
+
+ >
+ );
+};
+
+MemberProfileDetail.getLayout = function getLayout(page: ReactElement) {
+ return (
+
+ {page}
+
+ );
+};
+
+export default MemberProfileDetail;
diff --git a/frontend/src/pages/directory/index.tsx b/frontend/src/pages/directory/index.tsx
new file mode 100644
index 0000000..50a03a6
--- /dev/null
+++ b/frontend/src/pages/directory/index.tsx
@@ -0,0 +1,129 @@
+import React, { ReactElement, useEffect, useState, useMemo } from 'react';
+import Head from 'next/head';
+import { mdiMagnify } from '@mdi/js';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import { fetch } from '../../stores/member_profiles/member_profilesSlice';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import { getPageTitle } from '../../config';
+import MemberCard from '../../components/Directory/MemberCard';
+import BaseIcon from '../../components/BaseIcon';
+import LoadingSpinner from '../../components/LoadingSpinner';
+import { debounce } from 'lodash';
+
+const ExecutiveDirectory = () => {
+ const dispatch = useAppDispatch();
+ const { member_profiles, loading } = useAppSelector((state) => state.member_profiles);
+ const [searchTerm, setSearchTerm] = useState('');
+ const [selectedSector, setSelectedSector] = useState('all');
+
+ // Load data on mount
+ useEffect(() => {
+ const currentDate = new Date().toISOString();
+ // Base filters: active status, directory consent, and reconfirmation is current
+ const baseQuery = `?profile_status=active&directory_consent=true&confirmation_expires_atRange=${currentDate}&limit=100`;
+ dispatch(fetch({ query: baseQuery }));
+ }, [dispatch]);
+
+ // Client-side filtering for live search and sector
+ // Note: For a real large-scale app, we'd do this server-side,
+ // but for the directory experience requested, client-side live filtering is smoother if data size allows.
+ const filteredMembers = useMemo(() => {
+ if (!member_profiles || !Array.isArray(member_profiles)) return [];
+
+ return member_profiles.filter((member: any) => {
+ const matchesSector =
+ selectedSector === 'all' ||
+ member.sector === selectedSector;
+
+ const searchLower = searchTerm.toLowerCase();
+ const fullName = `${member.user?.firstName || ''} ${member.user?.lastName || ''}`.toLowerCase();
+ const matchesSearch =
+ fullName.includes(searchLower) ||
+ (member.professional_title || '').toLowerCase().includes(searchLower) ||
+ (member.organization || '').toLowerCase().includes(searchLower) ||
+ (member.industry || '').toLowerCase().includes(searchLower);
+
+ return matchesSector && matchesSearch;
+ });
+ }, [member_profiles, searchTerm, selectedSector]);
+
+ const handleSearchChange = (e: React.ChangeEvent) => {
+ setSearchTerm(e.target.value);
+ };
+
+ return (
+ <>
+
+ {getPageTitle('Executive Directory')}
+
+
+
+ {/* Header / Search & Filter Bar */}
+
+ {/* Search Bar */}
+
+
+ {/* Sector Filters */}
+
+ {[
+ { id: 'all', label: 'All Sectors' },
+ { id: 'corporate', label: 'Corporate' },
+ { id: 'nonprofit_501c3', label: 'Nonprofit (501c3)' },
+ ].map((sector) => (
+
+ ))}
+
+
+
+ {/* Directory Grid */}
+ {loading ? (
+
+
+
+ ) : filteredMembers.length > 0 ? (
+
+ {filteredMembers.map((member: any) => (
+
+ ))}
+
+ ) : (
+
+
No members found matching your criteria.
+
+ )}
+
+ >
+ );
+};
+
+ExecutiveDirectory.getLayout = function getLayout(page: ReactElement) {
+ return (
+
+ {page}
+
+ );
+};
+
+export default ExecutiveDirectory;
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index 7f89744..ddcd9b6 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -70,7 +70,7 @@ export default function Starter() {
Enter Executive Directory
@@ -98,4 +98,4 @@ export default function Starter() {
Starter.getLayout = function getLayout(page: ReactElement) {
return {page};
-};
+};
\ No newline at end of file
diff --git a/frontend/src/styles.ts b/frontend/src/styles.ts
index a969b60..045167b 100644
--- a/frontend/src/styles.ts
+++ b/frontend/src/styles.ts
@@ -31,18 +31,18 @@ export const white: StyleObject = {
asideMenuItem: 'text-gray-700 hover:bg-gray-100/70 dark:text-dark-500 dark:hover:text-white dark:hover:bg-dark-800',
asideMenuItemActive: 'font-bold text-black dark:text-white',
asideMenuDropdown: 'bg-gray-100/75',
- navBarItemLabel: 'text-blue-600',
+ navBarItemLabel: 'text-pavitra-blue',
navBarItemLabelHover: 'hover:text-black',
navBarItemLabelActiveColor: 'text-black',
overlay: 'from-white via-gray-100 to-white',
activeLinkColor: 'bg-gray-100/70',
bgLayoutColor: 'bg-gray-50',
- iconsColor: 'text-blue-500',
+ iconsColor: 'text-pavitra-blue',
cardsColor: 'bg-white',
- focusRingColor: 'focus:ring focus:ring-blue-600 focus:border-blue-600 focus:outline-none border-gray-300 dark:focus:ring-blue-600 dark:focus:border-blue-600',
+ focusRingColor: 'focus:ring focus:ring-pavitra-blue focus:border-pavitra-blue focus:outline-none border-gray-300 dark:focus:ring-pavitra-blue dark:focus:border-pavitra-blue',
corners: 'rounded',
cardsStyle: 'bg-white border border-pavitra-400',
- linkColor: 'text-blue-600',
+ linkColor: 'text-pavitra-blue',
websiteHeder: 'border-b border-gray-200',
borders: 'border-gray-200',
shadow: '',
@@ -88,14 +88,14 @@ export const basic: StyleObject = {
asideMenuItemActive: 'font-bold text-white',
asideMenuDropdown: 'bg-gray-700/50',
navBarItemLabel: 'text-black',
- navBarItemLabelHover: 'hover:text-blue-500',
- navBarItemLabelActiveColor: 'text-blue-600',
+ navBarItemLabelHover: 'hover:text-pavitra-blue',
+ navBarItemLabelActiveColor: 'text-pavitra-blue',
overlay: 'from-gray-700 via-gray-900 to-gray-700',
activeLinkColor: 'bg-gray-100/70',
bgLayoutColor: 'bg-gray-50',
- iconsColor: 'text-blue-500',
+ iconsColor: 'text-pavitra-blue',
cardsColor: 'bg-white',
- focusRingColor: 'focus:ring focus:ring-blue-600 focus:border-blue-600 focus:outline-none dark:focus:ring-blue-600 border-gray-300 dark:focus:border-blue-600',
+ focusRingColor: 'focus:ring focus:ring-pavitra-blue focus:border-pavitra-blue focus:outline-none dark:focus:ring-pavitra-blue border-gray-300 dark:focus:border-pavitra-blue',
corners: 'rounded',
cardsStyle: 'bg-white border border-pavitra-400',
linkColor: 'text-black',
@@ -104,4 +104,4 @@ export const basic: StyleObject = {
shadow: '',
websiteSectionStyle: '',
textSecondary: '',
-}
+}
\ No newline at end of file
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index a06c7d1..601d38d 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -53,7 +53,7 @@ module.exports = {
text: '#45B26B',
},
'pavitra': {
- 'blue': '#0162FD',
+ 'blue': '#091EAA',
'green': '#00B448',
'orange': '#FFAA00',
'red': '#F20041',
@@ -107,4 +107,4 @@ module.exports = {
);
}),
],
-}
+}
\ No newline at end of file