Route client users to portal experience
This commit is contained in:
parent
a6aef66d1e
commit
928e45b084
@ -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
|
||||
</p>
|
||||
<p className='mt-2 text-sm leading-6 text-[#72798a]'>
|
||||
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.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -242,7 +263,9 @@ export default function LayoutAuthenticated({ children, permission }: Props) {
|
||||
Workspace status
|
||||
</p>
|
||||
<p className='mt-1 text-sm text-[#72798a]'>
|
||||
Review sessions, clients, tasks, and shared client materials.
|
||||
{isClientUser
|
||||
? 'Review your commitments, notes, and shared resources.'
|
||||
: 'Review sessions, clients, tasks, and shared client materials.'}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
|
||||
@ -13,6 +13,7 @@ import BaseIcon from '../components/BaseIcon';
|
||||
import SectionMain from '../components/SectionMain';
|
||||
import { getPageTitle } from '../config';
|
||||
import LayoutAuthenticated from '../layouts/Authenticated';
|
||||
import { useAppSelector } from '../stores/hooks';
|
||||
|
||||
type PortalClient = {
|
||||
id: string;
|
||||
@ -45,26 +46,38 @@ function Panel({
|
||||
}
|
||||
|
||||
const ClientPortal = () => {
|
||||
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<PortalClient | null>(
|
||||
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 = () => {
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<div className='mx-auto max-w-7xl'>
|
||||
<div className='mb-4 grid gap-4 xl:grid-cols-[0.95fr_1.05fr]'>
|
||||
<div
|
||||
className={`mb-4 grid gap-4 ${
|
||||
isClientUser ? '' : 'xl:grid-cols-[0.95fr_1.05fr]'
|
||||
}`}
|
||||
>
|
||||
<div className='rounded-lg bg-[#19192d] p-5 text-white'>
|
||||
<div className='flex items-center gap-3 text-[#b17a1e]'>
|
||||
<BaseIcon path={mdiAccountCircle} size={18} />
|
||||
@ -95,33 +112,36 @@ const ClientPortal = () => {
|
||||
</span>
|
||||
</div>
|
||||
<h1 className='mt-3 text-xl font-semibold'>
|
||||
Client portal preview
|
||||
{isClientUser ? 'Your client portal' : 'Client portal preview'}
|
||||
</h1>
|
||||
<p className='mt-2 max-w-2xl text-sm leading-6 text-[#fffdf9]'>
|
||||
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.'}
|
||||
</p>
|
||||
</div>
|
||||
<Panel className='p-4'>
|
||||
<label className='block text-xs font-semibold uppercase tracking-[0.22em] text-[#b17a1e]'>
|
||||
Preview as client
|
||||
</label>
|
||||
<select
|
||||
value={clientId}
|
||||
onChange={(event) => 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) => (
|
||||
<option key={client.id} value={client.id}>
|
||||
{client.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className='mt-4 text-sm leading-6 text-[#72798a]'>
|
||||
MVP note: this is still a coach-visible preview. Final client
|
||||
access should be a client role or magic-link route.
|
||||
</p>
|
||||
</Panel>
|
||||
{!isClientUser && (
|
||||
<Panel className='p-4'>
|
||||
<label className='block text-xs font-semibold uppercase tracking-[0.22em] text-[#b17a1e]'>
|
||||
Preview as client
|
||||
</label>
|
||||
<select
|
||||
value={clientId}
|
||||
onChange={(event) => 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) => (
|
||||
<option key={client.id} value={client.id}>
|
||||
{client.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className='mt-4 text-sm leading-6 text-[#72798a]'>
|
||||
Coaches can preview what each client sees before sharing
|
||||
notes, commitments, or resources.
|
||||
</p>
|
||||
</Panel>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{portalClient && (
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user