diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx
index 72935e6..4ced3eb 100644
--- a/frontend/src/components/NavBarItem.tsx
+++ b/frontend/src/components/NavBarItem.tsx
@@ -1,6 +1,5 @@
-import React, {useEffect, useRef} from 'react'
+import React, {useEffect, useRef, useState} from 'react'
import Link from 'next/link'
-import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
@@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) {
}
return
{NavBarItemComponentContents}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/config.ts b/frontend/src/config.ts
index a9783c8..3adcea7 100644
--- a/frontend/src/config.ts
+++ b/frontend/src/config.ts
@@ -8,8 +8,8 @@ export const localStorageStyleKey = 'style'
export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20'
-export const appTitle = 'created by Flatlogic generator!'
+export const appTitle = 'The Power Suite Executive Directory'
export const getPageTitle = (currentPageTitle: string) => `${currentPageTitle} — ${appTitle}`
-export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || ''
+export const tinyKey = process.env.NEXT_PUBLIC_TINY_KEY || ''
\ No newline at end of file
diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx
index 1b9907d..bbc840e 100644
--- a/frontend/src/layouts/Authenticated.tsx
+++ b/frontend/src/layouts/Authenticated.tsx
@@ -1,5 +1,4 @@
-import React, { ReactNode, useEffect } from 'react'
-import { useState } from 'react'
+import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
@@ -63,6 +62,19 @@ export default function LayoutAuthenticated({
if (!hasPermission(currentUser, permission)) router.push('/error');
}, [currentUser, permission]);
+
+ // Mandatory Opt-In Gating
+ useEffect(() => {
+ if (!currentUser) return;
+
+ const memberProfile = currentUser?.member_profiles_user?.[0] || currentUser?.member_profiles_user;
+ const isOptInPage = router.pathname === '/profile/opt-in';
+
+ // If user is a member but hasn't consented, redirect to opt-in
+ if (memberProfile && !memberProfile.directory_consent && !isOptInPage) {
+ router.push('/profile/opt-in');
+ }
+ }, [currentUser, router.pathname]);
const darkMode = useAppSelector((state) => state.style.darkMode)
@@ -122,7 +134,7 @@ export default function LayoutAuthenticated({
onAsideLgClose={() => setIsAsideLgActive(false)}
/>
{children}
- Hand-crafted & Made with ❤️
+ Power Suite Executive Directory © 2026
)
diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index d4d027c..4325420 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -3,89 +3,70 @@ import { MenuAsideItem } from './interfaces'
const menuAside: MenuAsideItem[] = [
{
- href: '/dashboard',
- icon: icon.mdiViewDashboardOutline,
- label: 'Dashboard',
- },
-
- {
- href: '/users/users-list',
- label: 'Users',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- icon: icon.mdiAccountGroup ?? icon.mdiTable,
- permissions: 'READ_USERS'
- },
- {
- href: '/roles/roles-list',
- label: 'Roles',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
- permissions: 'READ_ROLES'
- },
- {
- href: '/permissions/permissions-list',
- label: 'Permissions',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
- permissions: 'READ_PERMISSIONS'
+ href: '/',
+ icon: icon.mdiHomeOutline,
+ label: 'Home',
},
{
href: '/member_profiles/member_profiles-list',
- label: 'Member profiles',
+ label: 'Executive Directory',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
- icon: 'mdiBadgeAccountOutline' in icon ? icon['mdiBadgeAccountOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
+ icon: icon.mdiCardAccountDetailsOutline ?? icon.mdiTable,
permissions: 'READ_MEMBER_PROFILES'
},
- {
- href: '/expertise_areas/expertise_areas-list',
- label: 'Expertise areas',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- icon: 'mdiLightbulbOnOutline' in icon ? icon['mdiLightbulbOnOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
- permissions: 'READ_EXPERTISE_AREAS'
- },
- {
- href: '/member_import_batches/member_import_batches-list',
- label: 'Member import batches',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- icon: 'mdiFileUploadOutline' in icon ? icon['mdiFileUploadOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
- permissions: 'READ_MEMBER_IMPORT_BATCHES'
- },
- {
- href: '/email_reminders/email_reminders-list',
- label: 'Email reminders',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- icon: 'mdiEmailOutline' in icon ? icon['mdiEmailOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
- permissions: 'READ_EMAIL_REMINDERS'
- },
- {
- href: '/audit_events/audit_events-list',
- label: 'Audit events',
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- // @ts-ignore
- icon: 'mdiClipboardTextOutline' in icon ? icon['mdiClipboardTextOutline' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
- permissions: 'READ_AUDIT_EVENTS'
- },
{
href: '/profile',
- label: 'Profile',
+ label: 'My Profile',
icon: icon.mdiAccountCircle,
},
-
-
{
- href: '/api-docs',
- target: '_blank',
- label: 'Swagger API',
- icon: icon.mdiFileCode,
- permissions: 'READ_API_DOCS'
+ label: 'Administration',
+ 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',
+ icon: icon.mdiAccountGroup,
+ },
+ {
+ href: '/member_import_batches/member_import_batches-list',
+ label: 'CSV Imports',
+ icon: icon.mdiFileUploadOutline,
+ },
+ {
+ href: '/expertise_areas/expertise_areas-list',
+ label: 'Expertise Areas',
+ icon: icon.mdiLightbulbOnOutline,
+ },
+ {
+ href: '/email_reminders/email_reminders-list',
+ label: 'System Emails',
+ icon: icon.mdiEmailOutline,
+ },
+ {
+ href: '/audit_events/audit_events-list',
+ label: 'Audit Log',
+ icon: icon.mdiClipboardTextOutline,
+ },
+ {
+ href: '/roles/roles-list',
+ label: 'Roles',
+ icon: icon.mdiShieldAccountVariantOutline,
+ },
+ {
+ href: '/permissions/permissions-list',
+ label: 'Permissions',
+ icon: icon.mdiShieldAccountOutline,
+ },
+ ]
},
]
-export default menuAside
+export default menuAside
\ No newline at end of file
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index f9196da..dd261ac 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,166 +1,94 @@
-
-import React, { useEffect, useState } from 'react';
-import type { ReactElement } from 'react';
+import React, { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
-import BaseButton from '../components/BaseButton';
-import CardBox from '../components/CardBox';
-import SectionFullScreen from '../components/SectionFullScreen';
+import { useRouter } from 'next/router';
+import { useAppSelector, useAppDispatch } from '../stores/hooks';
import LayoutGuest from '../layouts/Guest';
-import BaseDivider from '../components/BaseDivider';
-import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
-import { useAppSelector } from '../stores/hooks';
-import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
-import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
-
+import BaseButton from '../components/BaseButton';
+import { logoutUser } from '../stores/authSlice';
export default function Starter() {
- const [illustrationImage, setIllustrationImage] = useState({
- src: undefined,
- photographer: undefined,
- photographer_url: undefined,
- })
- const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
- const [contentType, setContentType] = useState('image');
- const [contentPosition, setContentPosition] = useState('background');
- const textColor = useAppSelector((state) => state.style.linkColor);
+ const router = useRouter();
+ const dispatch = useAppDispatch();
+ const { currentUser, token } = useAppSelector((state) => state.auth);
+
+ const handleLogout = () => {
+ dispatch(logoutUser());
+ router.push('/login');
+ };
- const title = 'Power Suite Executive Directory'
-
- // Fetch Pexels image/video
- useEffect(() => {
- async function fetchData() {
- const image = await getPexelsImage();
- const video = await getPexelsVideo();
- setIllustrationImage(image);
- setIllustrationVideo(video);
- }
- fetchData();
- }, []);
-
- const imageBlock = (image) => (
-
-
+ // If not authenticated, show a minimal landing with Login CTA
+ if (!token) {
+ return (
+
+
+
{getPageTitle('The Power Suite')}
+
+
+
+ Zetas in Corporate America: The Power Suite
+
+
+ A private executive directory connecting Power Suite members across industries, regions, and leadership levels.
+
+
+
+
+
);
+ }
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
- Your browser does not support the video tag.
-
-
-
)
- }
- };
-
+ // Option C Landing Page for Authenticated Users
return (
-
+
-
{getPageTitle('Starter Page')}
+
{getPageTitle('Home')}
+
+
+
+ Zetas in Corporate America: The Power Suite
+
+
+ A private executive directory connecting Power Suite members across industries, regions, and leadership levels.
+
+
-
-
- {contentType === 'image' && contentPosition !== 'background'
- ? imageBlock(illustrationImage)
- : null}
- {contentType === 'video' && contentPosition !== 'background'
- ? videoBlock(illustrationVideo)
- : null}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Update My Profile
+
+
+ Reconfirm My Profile
+
+
+ Log Out
+
+
-
-
-
© 2026 {title} . All rights reserved
-
- Privacy Policy
-
-
-
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
return {page} ;
-};
-
+};
\ No newline at end of file
diff --git a/frontend/src/pages/profile/opt-in.tsx b/frontend/src/pages/profile/opt-in.tsx
new file mode 100644
index 0000000..10752cf
--- /dev/null
+++ b/frontend/src/pages/profile/opt-in.tsx
@@ -0,0 +1,116 @@
+import React, { ReactElement, useState, useEffect } from 'react';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import { getPageTitle } from '../../config';
+import CardBox from '../../components/CardBox';
+import SectionMain from '../../components/SectionMain';
+import BaseButton from '../../components/BaseButton';
+import { findMe } from '../../stores/authSlice';
+import axios from 'axios';
+import { toast } from 'react-toastify';
+
+export default function ProfileOptIn() {
+ const router = useRouter();
+ const dispatch = useAppDispatch();
+ const { currentUser } = useAppSelector((state) => state.auth);
+ const [consent, setConsent] = useState(false);
+ const [loading, setLoading] = useState(false);
+
+ // Get the member profile ID. It might be in member_profiles_user array or object depending on associations
+ const memberProfile = currentUser?.member_profiles_user?.[0] || currentUser?.member_profiles_user;
+
+ useEffect(() => {
+ if (memberProfile?.directory_consent) {
+ router.push('/');
+ }
+ }, [memberProfile, router]);
+
+ const handleConsent = async () => {
+ if (!consent) {
+ toast.error('You must provide consent to proceed.');
+ return;
+ }
+
+ setLoading(true);
+ try {
+ // Update member profile with consent
+ await axios.put(`/member_profiles/${memberProfile.id}`, {
+ data: {
+ directory_consent: true,
+ consented_at: new Date(),
+ profile_status: 'draft' // Initial status after consent
+ }
+ });
+
+ await dispatch(findMe());
+ toast.success('Consent recorded. Please complete your profile.');
+ router.push('/member_profiles/member_profiles-form');
+ } catch (error) {
+ console.error(error);
+ toast.error('Failed to save consent. Please try again.');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <>
+
+ {getPageTitle('Directory Consent')}
+
+
+
+
+
+
+
Private Directory Consent
+
+ Welcome to Zetas in Corporate America: The Power Suite. To access and be visible in our private executive directory, we require your explicit consent.
+
+
+
+
+
Mandatory Disclosure
+
+ I understand this directory is visible only to approved Power Suite members and I consent to having my professional information displayed.
+
+
+ Your profile will remain in a "Draft" status and will not be searchable by other members until you complete all required fields, including your headshot and LinkedIn URL.
+
+
+
+
+ setConsent(e.target.checked)}
+ />
+
+ I agree to the terms above and wish to opt-in to the Executive Directory.
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+ProfileOptIn.getLayout = function getLayout(page: ReactElement) {
+ return {page} ;
+};
\ No newline at end of file