From 928e45b0848c062412cd8e8b52226b748e5a4064 Mon Sep 17 00:00:00 2001
From: Flatlogic Bot
Date: Tue, 9 Jun 2026 13:31:08 +0000
Subject: [PATCH] Route client users to portal experience
---
frontend/src/layouts/Authenticated.tsx | 29 +++++++++--
frontend/src/pages/client-portal.tsx | 72 ++++++++++++++++----------
frontend/src/pages/login.tsx | 7 ++-
3 files changed, 78 insertions(+), 30 deletions(-)
diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx
index 97d1589..fc0c459 100644
--- a/frontend/src/layouts/Authenticated.tsx
+++ b/frontend/src/layouts/Authenticated.tsx
@@ -62,6 +62,7 @@ export default function LayoutAuthenticated({ children, permission }: Props) {
const router = useRouter();
const { token, currentUser } = useAppSelector((state) => state.auth);
const [isAsideOpen, setIsAsideOpen] = useState(false);
+ const isClientUser = currentUser?.app_role?.name === 'Client';
function getLocalToken() {
if (typeof window === 'undefined') {
@@ -109,6 +110,21 @@ export default function LayoutAuthenticated({ children, permission }: Props) {
}
}, [currentUser, permission]);
+ useEffect(() => {
+ if (!isClientUser) {
+ return;
+ }
+
+ if (
+ router.pathname === '/client-portal' ||
+ router.pathname === '/profile'
+ ) {
+ return;
+ }
+
+ router.push('/client-portal');
+ }, [isClientUser, router.pathname]);
+
useEffect(() => {
function closeAside() {
setIsAsideOpen(false);
@@ -122,6 +138,10 @@ export default function LayoutAuthenticated({ children, permission }: Props) {
}, [router.events]);
const visibleNavItems = navItems.filter((item) => {
+ if (isClientUser) {
+ return item.href === '/client-portal' || item.href === '/profile';
+ }
+
if (!item.permission) {
return true;
}
@@ -170,8 +190,9 @@ export default function LayoutAuthenticated({ children, permission }: Props) {
Today
- Prepare, follow up, and keep every coaching relationship warm
- between sessions.
+ {isClientUser
+ ? 'Review shared notes, commitments, and resources from your coach.'
+ : 'Prepare, follow up, and keep every coaching relationship warm between sessions.'}
@@ -242,7 +263,9 @@ export default function LayoutAuthenticated({ children, permission }: Props) {
Workspace status
- Review sessions, clients, tasks, and shared client materials.
+ {isClientUser
+ ? 'Review your commitments, notes, and shared resources.'
+ : 'Review sessions, clients, tasks, and shared client materials.'}
{
+ const { currentUser } = useAppSelector((state) => state.auth);
const [clients, setClients] = React.useState<
- Array<{ id: string; name: string }>
+ Array<{ id: string; name: string; email?: string }>
>([]);
const [clientId, setClientId] = React.useState('');
const [portalClient, setPortalClient] = React.useState(
null,
);
+ const isClientUser = currentUser?.app_role?.name === 'Client';
+
React.useEffect(() => {
async function loadClients() {
const response = await axios.get('/coaching/clients');
setClients(response.data);
if (response.data.length > 0) {
+ const currentClient = response.data.find((client) => {
+ return client.email === currentUser?.email;
+ });
+
+ if (isClientUser && currentClient) {
+ setClientId(currentClient.id);
+ return;
+ }
+
setClientId(response.data[0].id);
}
}
loadClients();
- }, []);
+ }, [currentUser?.email, isClientUser]);
React.useEffect(() => {
async function loadPortal() {
@@ -86,7 +99,11 @@ const ClientPortal = () => {
-
+
@@ -95,33 +112,36 @@ const ClientPortal = () => {
- Client portal preview
+ {isClientUser ? 'Your client portal' : 'Client portal preview'}
- Preview the client-facing workspace with shared notes,
- commitments, resources, and a pre-session reflection prompt.
+ {isClientUser
+ ? 'Review shared notes, commitments, resources, and reflections for your coaching work.'
+ : 'Preview the client-facing workspace with shared notes, commitments, resources, and a pre-session reflection prompt.'}
-
-
- Preview as client
-
- setClientId(event.target.value)}
- className='mt-4 w-full rounded-lg border border-[#19192d]/10 bg-white px-3 py-2 text-[#19192d] outline-none focus:border-[#35b7a5] focus:ring-2 focus:ring-[#35b7a5]/15'
- >
- {clients.map((client) => (
-
- {client.name}
-
- ))}
-
-
- MVP note: this is still a coach-visible preview. Final client
- access should be a client role or magic-link route.
-
-
+ {!isClientUser && (
+
+
+ Preview as client
+
+ setClientId(event.target.value)}
+ className='mt-4 w-full rounded-lg border border-[#19192d]/10 bg-white px-3 py-2 text-[#19192d] outline-none focus:border-[#35b7a5] focus:ring-2 focus:ring-[#35b7a5]/15'
+ >
+ {clients.map((client) => (
+
+ {client.name}
+
+ ))}
+
+
+ Coaches can preview what each client sees before sharing
+ notes, commitments, or resources.
+
+
+ )}
{portalClient && (
diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx
index db3c1f4..3ab965b 100644
--- a/frontend/src/pages/login.tsx
+++ b/frontend/src/pages/login.tsx
@@ -120,9 +120,14 @@ export default function Login() {
useEffect(() => {
if (currentUser?.id) {
+ if (currentUser?.app_role?.name === 'Client') {
+ router.push('/client-portal');
+ return;
+ }
+
router.push('/dashboard');
}
- }, [currentUser?.id, router]);
+ }, [currentUser?.id, currentUser?.app_role?.name, router]);
useEffect(() => {
if (errorMessage) {