diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx
index cc97f6a..14ee234 100644
--- a/frontend/src/layouts/Authenticated.tsx
+++ b/frontend/src/layouts/Authenticated.tsx
@@ -1,129 +1,261 @@
-import React, { ReactNode, useEffect } from 'react'
-import { useState } from 'react'
+import {
+ mdiAccountCircle,
+ mdiAccountGroup,
+ mdiBookOpenVariant,
+ mdiClose,
+ mdiFileDocumentEditOutline,
+ mdiLogout,
+ mdiMenu,
+ mdiShieldAccountOutline,
+ mdiViewDashboardOutline,
+} from '@mdi/js';
import jwt from 'jsonwebtoken';
-import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
-import menuAside from '../menuAside'
-import menuNavBar from '../menuNavBar'
-import BaseIcon from '../components/BaseIcon'
-import NavBar from '../components/NavBar'
-import NavBarItemPlain from '../components/NavBarItemPlain'
-import AsideMenu from '../components/AsideMenu'
-import FooterBar from '../components/FooterBar'
-import { useAppDispatch, useAppSelector } from '../stores/hooks'
-import Search from '../components/Search';
-import { useRouter } from 'next/router'
-import {findMe, logoutUser} from "../stores/authSlice";
-
-import {hasPermission} from "../helpers/userPermissions";
-
+import Link from 'next/link';
+import { useRouter } from 'next/router';
+import React, { ReactNode, useEffect, useState } from 'react';
+import BaseIcon from '../components/BaseIcon';
+import { hasPermission } from '../helpers/userPermissions';
+import { findMe, logoutUser } from '../stores/authSlice';
+import { useAppDispatch, useAppSelector } from '../stores/hooks';
type Props = {
- children: ReactNode
-
- permission?: string
-
-}
+ children: ReactNode;
+ permission?: string;
+};
-export default function LayoutAuthenticated({
- children,
-
- permission
-
-}: Props) {
- const dispatch = useAppDispatch()
- const router = useRouter()
- const { token, currentUser } = useAppSelector((state) => state.auth)
- const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
- let localToken
- if (typeof window !== 'undefined') {
- // Perform localStorage action
- localToken = localStorage.getItem('token')
+const navItems = [
+ {
+ href: '/dashboard',
+ icon: mdiViewDashboardOutline,
+ label: 'Dashboard',
+ },
+ {
+ href: '/clients',
+ icon: mdiAccountGroup,
+ label: 'Clients',
+ },
+ {
+ href: '/session-memory',
+ icon: mdiFileDocumentEditOutline,
+ label: 'Session Memory',
+ },
+ {
+ href: '/client-portal',
+ icon: mdiBookOpenVariant,
+ label: 'Client Portal',
+ },
+ {
+ href: '/users/users-list',
+ icon: mdiShieldAccountOutline,
+ label: 'Team',
+ permission: 'READ_USERS',
+ },
+ {
+ href: '/profile',
+ icon: mdiAccountCircle,
+ label: 'Profile',
+ },
+];
+
+export default function LayoutAuthenticated({ children, permission }: Props) {
+ const dispatch = useAppDispatch();
+ const router = useRouter();
+ const { token, currentUser } = useAppSelector((state) => state.auth);
+ const [isAsideOpen, setIsAsideOpen] = useState(false);
+
+ function getLocalToken() {
+ if (typeof window === 'undefined') {
+ return null;
+ }
+
+ return localStorage.getItem('token');
}
- const isTokenValid = () => {
- const token = localStorage.getItem('token');
- if (!token) return;
- const date = new Date().getTime() / 1000;
- const data = jwt.decode(token);
- if (!data) return;
- return date < data.exp;
- };
+ function isTokenValid() {
+ const localToken = getLocalToken();
+ if (!localToken) {
+ return false;
+ }
+
+ const decodedToken = jwt.decode(localToken);
+ if (!decodedToken || typeof decodedToken === 'string') {
+ return false;
+ }
+
+ if (!decodedToken.exp) {
+ return false;
+ }
+
+ const now = new Date().getTime() / 1000;
+ return now < decodedToken.exp;
+ }
useEffect(() => {
dispatch(findMe());
+
if (!isTokenValid()) {
dispatch(logoutUser());
router.push('/login');
}
- }, [token, localToken]);
+ }, [token]);
-
useEffect(() => {
- if (!permission || !currentUser) return;
+ if (!permission || !currentUser) {
+ return;
+ }
- if (!hasPermission(currentUser, permission)) router.push('/error');
+ if (!hasPermission(currentUser, permission)) {
+ router.push('/error');
+ }
}, [currentUser, permission]);
-
-
- const darkMode = useAppSelector((state) => state.style.darkMode)
-
- const [isAsideMobileExpanded, setIsAsideMobileExpanded] = useState(false)
- const [isAsideLgActive, setIsAsideLgActive] = useState(false)
useEffect(() => {
- const handleRouteChangeStart = () => {
- setIsAsideMobileExpanded(false)
- setIsAsideLgActive(false)
+ function closeAside() {
+ setIsAsideOpen(false);
}
- router.events.on('routeChangeStart', handleRouteChangeStart)
+ router.events.on('routeChangeStart', closeAside);
- // If the component is unmounted, unsubscribe
- // from the event with the `off` method:
return () => {
- router.events.off('routeChangeStart', handleRouteChangeStart)
+ router.events.off('routeChangeStart', closeAside);
+ };
+ }, [router.events]);
+
+ const visibleNavItems = navItems.filter((item) => {
+ if (!item.permission) {
+ return true;
}
- }, [router.events, dispatch])
+ if (!currentUser) {
+ return false;
+ }
- const layoutAsidePadding = 'xl:pl-60'
+ return hasPermission(currentUser, item.permission);
+ });
+
+ const activePath = router.pathname.split('/')[1];
return (
-
-
+
+
+ {isAsideOpen && (
+
- )
+ );
}
diff --git a/frontend/src/pages/client-portal.tsx b/frontend/src/pages/client-portal.tsx
index b105812..11bd1fe 100644
--- a/frontend/src/pages/client-portal.tsx
+++ b/frontend/src/pages/client-portal.tsx
@@ -1,13 +1,18 @@
-import * as icon from '@mdi/js';
-import Head from 'next/head'
-import React from 'react'
+import {
+ mdiAccountCircle,
+ mdiBookOpenVariant,
+ mdiCheckCircleOutline,
+ mdiChevronRight,
+ mdiMessageReplyTextOutline,
+} from '@mdi/js';
import axios from 'axios';
-import type { ReactElement } from 'react'
-import LayoutAuthenticated from '../layouts/Authenticated'
-import SectionMain from '../components/SectionMain'
-import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
-import CardBox from '../components/CardBox'
-import { getPageTitle } from '../config'
+import Head from 'next/head';
+import React from 'react';
+import type { ReactElement } from 'react';
+import BaseIcon from '../components/BaseIcon';
+import SectionMain from '../components/SectionMain';
+import { getPageTitle } from '../config';
+import LayoutAuthenticated from '../layouts/Authenticated';
type PortalClient = {
id: string;
@@ -15,18 +20,44 @@ type PortalClient = {
goals?: string;
sessions?: Array<{ id: string; title: string; shared_client_notes?: string }>;
action_items?: Array<{ id: string; title: string; status: string }>;
- resources?: Array<{ id: string; title: string; description?: string; url?: string }>;
+ resources?: Array<{
+ id: string;
+ title: string;
+ description?: string;
+ url?: string;
+ }>;
};
+function Panel({
+ children,
+ className = '',
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return (
+
+ );
+}
+
const ClientPortal = () => {
- const [clients, setClients] = React.useState
>([]);
+ const [clients, setClients] = React.useState<
+ Array<{ id: string; name: string }>
+ >([]);
const [clientId, setClientId] = React.useState('');
- const [portalClient, setPortalClient] = React.useState(null);
+ const [portalClient, setPortalClient] = React.useState(
+ null,
+ );
React.useEffect(() => {
async function loadClients() {
const response = await axios.get('/coaching/clients');
setClients(response.data);
+
if (response.data.length > 0) {
setClientId(response.data[0].id);
}
@@ -54,75 +85,182 @@ const ClientPortal = () => {
{getPageTitle('Client Portal')}
-
- {''}
-
-
-
-
-
-
-
- {portalClient && (
-
-
- Your coaching workspace
- {portalClient.name}
- {portalClient.goals}
-
- Shared session notes
-
- {(portalClient.sessions || []).map((session) => (
-
-
{session.title}
-
{session.shared_client_notes}
-
- ))}
+
+
+
+
+
+
+ Client portal
+
-
-
-
-
- Commitments
-
- {(portalClient.action_items || []).map((item) => (
-
-
{item.title}
-
{item.status.replace('_', ' ')}
-
- ))}
-
-
-
-
- Resources
-
-
+
+ A private space for the client.
+
+
+ This preview shows the simpler coachee experience: shared notes,
+ commitments, resources, and a reflection prompt before the next
+ session.
+
+
+
+
+
+ MVP note: this is still a coach-visible preview. Final client
+ access should be a client role or magic-link route.
+
+
- )}
+
+ {portalClient && (
+
+
+
+
+ Your coaching workspace
+
+
+ {portalClient.name}
+
+
+ {portalClient.goals}
+
+
+
+
+
+
+
+
+
+
+ Shared session notes
+
+
+ Only coach-approved notes appear here.
+
+
+
+
+
+ {(portalClient.sessions || []).map((session) => (
+
+
+ {session.title}
+
+
+ {session.shared_client_notes}
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Commitments
+
+
+
+ {(portalClient.action_items || []).map((item) => (
+
+
+
+
+
+
+ {item.title}
+
+
+ {item.status.replace('_', ' ')}
+
+
+
+ ))}
+
+
+
+
+
+
+ Resources
+
+
+
+
+
+
+
+ Pre-session reflection
+
+
+ What changed since last time?
+
+
+ Final MVP should let the client answer this before a
+ session, then feed the response into the coach prep brief.
+
+
+
+
+ )}
+
>
- )
-}
+ );
+};
ClientPortal.getLayout = function getLayout(page: ReactElement) {
- return
{page}
-}
+ return
{page};
+};
-export default ClientPortal
+export default ClientPortal;
diff --git a/frontend/src/pages/clients.tsx b/frontend/src/pages/clients.tsx
index 251dfec..55ebeb0 100644
--- a/frontend/src/pages/clients.tsx
+++ b/frontend/src/pages/clients.tsx
@@ -1,13 +1,19 @@
-import * as icon from '@mdi/js';
-import Head from 'next/head'
-import React from 'react'
+import {
+ mdiAccountGroup,
+ mdiCalendarClock,
+ mdiCheckCircleOutline,
+ mdiFileDocumentOutline,
+ mdiTarget,
+} from '@mdi/js';
import axios from 'axios';
-import type { ReactElement } from 'react'
-import LayoutAuthenticated from '../layouts/Authenticated'
-import SectionMain from '../components/SectionMain'
-import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
-import CardBox from '../components/CardBox'
-import { getPageTitle } from '../config'
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import React from 'react';
+import type { ReactElement } from 'react';
+import BaseIcon from '../components/BaseIcon';
+import SectionMain from '../components/SectionMain';
+import { getPageTitle } from '../config';
+import LayoutAuthenticated from '../layouts/Authenticated';
type ActionItem = {
id: string;
@@ -31,6 +37,7 @@ type Client = {
company?: string;
role_title?: string;
tags?: string;
+ next_session_at?: string;
sessions?: Session[];
action_items?: ActionItem[];
package?: {
@@ -39,7 +46,32 @@ type Client = {
};
};
+function Panel({
+ children,
+ className = '',
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return (
+
+ );
+}
+
+function EmptyState({ label }: { label: string }) {
+ return (
+
+ {label}
+
+ );
+}
+
const Clients = () => {
+ const router = useRouter();
const [clients, setClients] = React.useState
([]);
const [selectedClientId, setSelectedClientId] = React.useState('');
@@ -47,15 +79,32 @@ const Clients = () => {
async function loadClients() {
const response = await axios.get('/coaching/clients');
setClients(response.data);
+
+ const queryClientId = router.query.clientId;
+ if (typeof queryClientId === 'string') {
+ setSelectedClientId(queryClientId);
+ return;
+ }
+
if (response.data.length > 0) {
setSelectedClientId(response.data[0].id);
}
}
- loadClients();
- }, []);
+ if (router.isReady) {
+ loadClients();
+ }
+ }, [router.isReady]);
- const selectedClient = clients.find((client) => client.id === selectedClientId) || clients[0];
+ const selectedClient =
+ clients.find((client) => client.id === selectedClientId) || clients[0];
+
+ function selectClient(clientId: string) {
+ setSelectedClientId(clientId);
+ router.replace(`/clients?clientId=${clientId}`, undefined, {
+ shallow: true,
+ });
+ }
return (
<>
@@ -63,85 +112,203 @@ const Clients = () => {
{getPageTitle('Clients')}
-
- {''}
-
-
-
-
-
- {clients.map((client) => (
-
setSelectedClientId(client.id)}
- className={`w-full rounded border p-4 text-left ${selectedClient?.id === client.id ? 'border-emerald-500 bg-emerald-50' : 'border-gray-200 bg-white'}`}
- >
-
-
-
{client.name}
-
{client.role_title} · {client.company}
-
-
{client.status}
-
-
- ))}
+
+
+
+
+
+
+ Client CRM
+
+
+
+ Relationship memory hub
+
+
+ Open any client and see goals, coaching notes, recent sessions,
+ and commitments without digging through scattered docs.
+
-
+
+ {clients.length} clients
+
+
- {selectedClient && (
-
-
-
{selectedClient.package?.title || 'Coaching client'}
-
{selectedClient.name}
-
{selectedClient.email}
+
+
+
+
+ Coaching relationships
+
-
-
-
-
Goals
-
{selectedClient.goals}
-
-
-
Coach Notes
-
{selectedClient.notes}
-
-
-
-
-
-
Recent Sessions
-
- {(selectedClient.sessions || []).map((session) => (
-
-
{session.title}
-
{session.ai_summary}
+
+ {clients.map((client) => (
+
selectClient(client.id)}
+ className={`block w-full p-5 text-left transition ${
+ selectedClient?.id === client.id
+ ? 'bg-[#e8f1ec]'
+ : 'bg-white hover:bg-[#fbfaf6]'
+ }`}
+ >
+
+
+
+ {client.name}
+
+
+ {client.role_title} · {client.company}
+
- ))}
-
-
-
-
Action Items
-
- {(selectedClient.action_items || []).map((item) => (
-
-
{item.title}
-
{item.status.replace('_', ' ')}
+
+ {client.status}
+
+
+
+ {(client.tags || '')
+ .split(',')
+ .map((tag) => tag.trim())
+ .filter(Boolean)
+ .slice(0, 3)
+ .map((tag) => (
+
+ {tag}
+
+ ))}
+
+
+ ))}
+
+
+
+ {selectedClient && (
+
+
+
+
+
+ {selectedClient.package?.title || 'Coaching client'}
+
+
+ {selectedClient.name}
+
+
+ {selectedClient.email}
+
+
+
+
+
+ Next session
- ))}
+
+ {selectedClient.next_session_at ||
+ 'No session scheduled'}
+
+
+
+
+
+
+
+ Goals
+
+
+ {selectedClient.goals}
+
+
+
+
+
+ Private coach notes
+
+
+ {selectedClient.notes}
+
+
+
+
+
+
+
+
+
+ Session timeline
+
+
+
+ {(selectedClient.sessions || []).length > 0 ? (
+ (selectedClient.sessions || []).map((session) => (
+
+
+ {session.title}
+
+
+ {session.ai_summary}
+
+
+ ))
+ ) : (
+
+ )}
+
+
+
+
+
+
+ Open commitments
+
+
+
+ {(selectedClient.action_items || []).length > 0 ? (
+ (selectedClient.action_items || []).map((item) => (
+
+
+
+
+
+
+ {item.title}
+
+
+ {item.status.replace('_', ' ')}
+
+
+
+ ))
+ ) : (
+
+ )}
+
+
-
- )}
+ )}
+
>
- )
-}
+ );
+};
Clients.getLayout = function getLayout(page: ReactElement) {
- return
{page}
-}
+ return
{page};
+};
-export default Clients
+export default Clients;
diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx
index 41a504b..cf9c965 100644
--- a/frontend/src/pages/dashboard.tsx
+++ b/frontend/src/pages/dashboard.tsx
@@ -1,15 +1,20 @@
-import * as icon from '@mdi/js';
-import Head from 'next/head'
-import React from 'react'
+import {
+ mdiAccountGroup,
+ mdiArrowRight,
+ mdiCheckCircleOutline,
+ mdiClockOutline,
+ mdiFileDocumentEditOutline,
+ mdiViewDashboardOutline,
+} from '@mdi/js';
import axios from 'axios';
-import type { ReactElement } from 'react'
+import Head from 'next/head';
import Link from 'next/link';
-import LayoutAuthenticated from '../layouts/Authenticated'
-import SectionMain from '../components/SectionMain'
-import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
-import CardBox from '../components/CardBox'
-import BaseIcon from '../components/BaseIcon'
-import { getPageTitle } from '../config'
+import React from 'react';
+import type { ReactElement } from 'react';
+import BaseIcon from '../components/BaseIcon';
+import SectionMain from '../components/SectionMain';
+import { getPageTitle } from '../config';
+import LayoutAuthenticated from '../layouts/Authenticated';
type Client = {
id: string;
@@ -54,6 +59,22 @@ const emptySummary: Summary = {
nextSessions: [],
};
+function ShellCard({
+ children,
+ className = '',
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return (
+
+ );
+}
+
const Dashboard = () => {
const [summary, setSummary] = React.useState
(emptySummary);
const [loading, setLoading] = React.useState(true);
@@ -68,12 +89,32 @@ const Dashboard = () => {
loadSummary();
}, []);
+ const nextSession = summary.nextSessions[0];
const stats = [
- ['Clients', summary.counts.clients, icon.mdiAccountGroup, '/clients'],
- ['Sessions', summary.counts.sessions, icon.mdiTable, '/session-memory'],
- ['Open actions', summary.counts.actionItems, icon.mdiTable, '/clients'],
- ['Shared resources', summary.counts.resources, icon.mdiTable, '/client-portal'],
- ['Prep briefs', summary.counts.prepBriefs, icon.mdiTable, '/session-memory'],
+ {
+ href: '/clients',
+ icon: mdiAccountGroup,
+ label: 'Active clients',
+ value: summary.counts.clients,
+ },
+ {
+ href: '/session-memory',
+ icon: mdiFileDocumentEditOutline,
+ label: 'Session memories',
+ value: summary.counts.sessions,
+ },
+ {
+ href: '/clients',
+ icon: mdiCheckCircleOutline,
+ label: 'Open commitments',
+ value: summary.counts.actionItems,
+ },
+ {
+ href: '/session-memory',
+ icon: mdiClockOutline,
+ label: 'Prep briefs',
+ value: summary.counts.prepBriefs,
+ },
];
return (
@@ -82,70 +123,182 @@ const Dashboard = () => {
{getPageTitle('Dashboard')}
-
- {''}
-
-
-
- {stats.map(([label, value, iconPath, href]) => (
-
-
-
-
-
{label}
-
{loading ? '...' : value}
-
-
-
-
-
- ))}
-
-
-
-
-
-
Active Clients
- View all
-
-
- {summary.activeClients.map((client) => (
-
-
-
-
{client.name}
-
{client.role_title} · {client.company}
-
-
{client.package?.title || 'Coaching package'}
-
+
+
+
+
+
+
+ Coach command center
+
+
+
+ Keep every client relationship moving between sessions.
+
+
+ Review the next conversation, follow up on commitments, and keep
+ session memory close to the work that needs attention today.
+
+
+
+ Generate session memory
- ))}
+
+ Open client records
+
+
-
-
-
-
Recent Session Memory
- Open memory
-
-
- {summary.nextSessions.map((session) => (
-
-
{session.title}
-
{session.client?.name}
-
{session.ai_summary}
+
+
+ Next session prep
+
+ {nextSession ? (
+
+
+ {nextSession.client?.name || 'Client session'}
+
+
+ {nextSession.title}
+
+
+ {nextSession.ai_summary}
+
+
+ Review memory
+
+
- ))}
-
-
+ ) : (
+
+ {loading
+ ? 'Loading your coaching workspace...'
+ : 'No upcoming session memory yet.'}
+
+ )}
+
+
+
+
+ {stats.map((stat) => (
+
+
+
+
+
+ {stat.label}
+
+
+ {loading ? '...' : stat.value}
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Relationship memory
+
+
+ Active clients
+
+
+
+ View all
+
+
+
+
+ {summary.activeClients.map((client) => (
+
+
+
+
+ {client.name}
+
+
+ {client.role_title} · {client.company}
+
+
+
+ {client.package?.title || 'Coaching'}
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Recent intelligence
+
+
+ Session memory
+
+
+
+ Open
+
+
+
+
+ {summary.nextSessions.map((session) => (
+
+
+ {session.title}
+
+
+ {session.client?.name}
+
+
+ {session.ai_summary}
+
+
+ ))}
+
+
+
>
- )
-}
+ );
+};
Dashboard.getLayout = function getLayout(page: ReactElement) {
- return
{page}
-}
+ return
{page};
+};
-export default Dashboard
+export default Dashboard;
diff --git a/frontend/src/pages/session-memory.tsx b/frontend/src/pages/session-memory.tsx
index 8bcc2b8..14e9c3d 100644
--- a/frontend/src/pages/session-memory.tsx
+++ b/frontend/src/pages/session-memory.tsx
@@ -1,14 +1,19 @@
-import * as icon from '@mdi/js';
-import Head from 'next/head'
-import React from 'react'
+import {
+ mdiCheckCircleOutline,
+ mdiContentCopy,
+ mdiFileDocumentEditOutline,
+ mdiLightbulbOnOutline,
+ mdiSendOutline,
+} from '@mdi/js';
import axios from 'axios';
-import type { ReactElement } from 'react'
-import LayoutAuthenticated from '../layouts/Authenticated'
-import SectionMain from '../components/SectionMain'
-import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
-import CardBox from '../components/CardBox'
-import BaseButton from '../components/BaseButton'
-import { getPageTitle } from '../config'
+import Head from 'next/head';
+import React from 'react';
+import type { ReactElement } from 'react';
+import BaseButton from '../components/BaseButton';
+import BaseIcon from '../components/BaseIcon';
+import SectionMain from '../components/SectionMain';
+import { getPageTitle } from '../config';
+import LayoutAuthenticated from '../layouts/Authenticated';
type Client = {
id: string;
@@ -25,12 +30,47 @@ type Session = {
client?: Client;
};
+function Panel({
+ children,
+ className = '',
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) {
+ return (
+
+ );
+}
+
+function OutputBlock({
+ title,
+ children,
+}: {
+ title: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+ {title}
+
+
{children}
+
+ );
+}
+
const SessionMemory = () => {
const [clients, setClients] = React.useState
([]);
const [sessions, setSessions] = React.useState([]);
const [clientId, setClientId] = React.useState('');
const [transcript, setTranscript] = React.useState('');
- const [generatedMemory, setGeneratedMemory] = React.useState(null);
+ const [generatedMemory, setGeneratedMemory] = React.useState(
+ null,
+ );
const [isGenerating, setIsGenerating] = React.useState(false);
async function loadData() {
@@ -40,6 +80,7 @@ const SessionMemory = () => {
]);
setClients(clientsResponse.data);
setSessions(sessionsResponse.data);
+
if (!clientId && clientsResponse.data.length > 0) {
setClientId(clientsResponse.data[0].id);
}
@@ -65,85 +106,175 @@ const SessionMemory = () => {
{getPageTitle('Session Memory')}
-
- {''}
-
-
-
-
- Extract a Session
-
-
-
-