Add coaching intake lead flow
This commit is contained in:
parent
677724e9f6
commit
2a10483480
@ -6,7 +6,6 @@ const passport = require('passport');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const bodyParser = require('body-parser');
|
||||
const db = require('./db/models');
|
||||
const config = require('./config');
|
||||
const swaggerUI = require('swagger-ui-express');
|
||||
const swaggerJsDoc = require('swagger-jsdoc');
|
||||
@ -18,6 +17,7 @@ const pexelsRoutes = require('./routes/pexels');
|
||||
|
||||
const openaiRoutes = require('./routes/openai');
|
||||
const coachingRoutes = require('./routes/coaching');
|
||||
const coachingPublicRoutes = require('./routes/coachingPublic');
|
||||
|
||||
|
||||
|
||||
@ -81,6 +81,7 @@ app.use(bodyParser.json());
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/file', fileRoutes);
|
||||
app.use('/api/pexels', pexelsRoutes);
|
||||
app.use('/api/coaching-public', coachingPublicRoutes);
|
||||
app.enable('trust proxy');
|
||||
|
||||
|
||||
|
||||
@ -70,6 +70,75 @@ router.get(
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/intake-leads",
|
||||
wrapAsync(async (req, res) => {
|
||||
const leads = await db.intake_leads.findAll({
|
||||
order: [["createdAt", "DESC"]],
|
||||
});
|
||||
|
||||
res.status(200).send(leads);
|
||||
}),
|
||||
);
|
||||
|
||||
router.patch(
|
||||
"/intake-leads/:id/status",
|
||||
wrapAsync(async (req, res) => {
|
||||
const lead = await db.intake_leads.findByPk(req.params.id);
|
||||
|
||||
if (!lead) {
|
||||
res.status(404).send({ error: "lead_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
await lead.update({
|
||||
status: req.body.status,
|
||||
updatedById: req.currentUser.id,
|
||||
});
|
||||
|
||||
res.status(200).send(lead);
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/intake-leads/:id/convert",
|
||||
wrapAsync(async (req, res) => {
|
||||
const lead = await db.intake_leads.findByPk(req.params.id);
|
||||
|
||||
if (!lead) {
|
||||
res.status(404).send({ error: "lead_not_found" });
|
||||
return;
|
||||
}
|
||||
|
||||
const client = await db.clients.create({
|
||||
name: lead.name,
|
||||
email: lead.email,
|
||||
company: lead.company,
|
||||
role_title: lead.role_title,
|
||||
status: "active",
|
||||
goals: lead.goal,
|
||||
notes: [
|
||||
lead.challenge && `Challenge: ${lead.challenge}`,
|
||||
lead.desired_outcome && `Desired outcome: ${lead.desired_outcome}`,
|
||||
lead.consent_ai_notes ? "Consented to AI-assisted session notes." : "AI notes consent not granted yet.",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n"),
|
||||
tags: "intake",
|
||||
ownerId: req.currentUser.id,
|
||||
createdById: req.currentUser.id,
|
||||
updatedById: req.currentUser.id,
|
||||
});
|
||||
|
||||
await lead.update({
|
||||
status: "converted",
|
||||
updatedById: req.currentUser.id,
|
||||
});
|
||||
|
||||
res.status(200).send({ lead, client });
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/clients",
|
||||
wrapAsync(async (req, res) => {
|
||||
|
||||
41
backend/src/routes/coachingPublic.js
Normal file
41
backend/src/routes/coachingPublic.js
Normal file
@ -0,0 +1,41 @@
|
||||
const express = require("express");
|
||||
const db = require("../db/models");
|
||||
const wrapAsync = require("../helpers").wrapAsync;
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post(
|
||||
"/intake",
|
||||
wrapAsync(async (req, res) => {
|
||||
const data = req.body || {};
|
||||
const name = String(data.name || "").trim();
|
||||
const email = String(data.email || "").trim();
|
||||
|
||||
if (!name) {
|
||||
res.status(400).send({ error: "name_required" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!email) {
|
||||
res.status(400).send({ error: "email_required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const lead = await db.intake_leads.create({
|
||||
name,
|
||||
email,
|
||||
company: data.company,
|
||||
role_title: data.role_title,
|
||||
goal: data.goal,
|
||||
challenge: data.challenge,
|
||||
desired_outcome: data.desired_outcome,
|
||||
source: data.source || "website",
|
||||
consent_ai_notes: Boolean(data.consent_ai_notes),
|
||||
status: "new",
|
||||
});
|
||||
|
||||
res.status(200).send(lead);
|
||||
}),
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
@ -4,6 +4,7 @@ import {
|
||||
mdiBookOpenVariant,
|
||||
mdiClose,
|
||||
mdiFileDocumentEditOutline,
|
||||
mdiFormTextbox,
|
||||
mdiLogout,
|
||||
mdiMenu,
|
||||
mdiShieldAccountOutline,
|
||||
@ -39,6 +40,11 @@ const navItems = [
|
||||
icon: mdiFileDocumentEditOutline,
|
||||
label: 'Session Memory',
|
||||
},
|
||||
{
|
||||
href: '/intake-leads',
|
||||
icon: mdiFormTextbox,
|
||||
label: 'Intake Leads',
|
||||
},
|
||||
{
|
||||
href: '/client-portal',
|
||||
icon: mdiBookOpenVariant,
|
||||
|
||||
@ -149,7 +149,7 @@ function Nav() {
|
||||
<a href='#control'>Trust</a>
|
||||
</nav>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`rounded-full px-5 py-2.5 text-sm font-semibold ${ui.button}`}
|
||||
>
|
||||
Start free
|
||||
@ -440,7 +440,7 @@ export default function HowItWorks() {
|
||||
</p>
|
||||
<div className='mt-9 flex flex-wrap justify-center gap-4'>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`rounded-full px-8 py-4 font-semibold ${ui.button}`}
|
||||
>
|
||||
Start free
|
||||
@ -624,7 +624,7 @@ export default function HowItWorks() {
|
||||
client portal, and approved follow-up prompts.
|
||||
</p>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`mt-8 inline-flex rounded-full px-7 py-4 font-semibold ${ui.button}`}
|
||||
>
|
||||
Start free
|
||||
@ -663,7 +663,7 @@ export default function HowItWorks() {
|
||||
</h2>
|
||||
<div className='mt-8 flex justify-center'>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`rounded-full px-8 py-4 font-semibold ${ui.button}`}
|
||||
>
|
||||
Start free
|
||||
|
||||
@ -179,7 +179,7 @@ export default function Starter() {
|
||||
<a href='#pricing'>Packages</a>
|
||||
</nav>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`rounded-full px-5 py-2.5 text-sm font-semibold ${ui.button}`}
|
||||
>
|
||||
Start free
|
||||
@ -207,7 +207,7 @@ export default function Starter() {
|
||||
</p>
|
||||
<div className='mt-9 flex justify-center'>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`rounded-full px-10 py-5 text-lg font-semibold ${ui.button}`}
|
||||
>
|
||||
Start free
|
||||
@ -503,7 +503,7 @@ export default function Starter() {
|
||||
</h2>
|
||||
</div>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`rounded-full px-6 py-3 text-center font-semibold ${ui.button}`}
|
||||
>
|
||||
Start your workspace
|
||||
@ -559,7 +559,7 @@ export default function Starter() {
|
||||
))}
|
||||
</div>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`mt-8 inline-flex rounded-full px-7 py-4 font-semibold ${ui.button}`}
|
||||
>
|
||||
Create workspace
|
||||
|
||||
205
frontend/src/pages/intake-leads.tsx
Normal file
205
frontend/src/pages/intake-leads.tsx
Normal file
@ -0,0 +1,205 @@
|
||||
import {
|
||||
mdiAccountConvertOutline,
|
||||
mdiArchiveOutline,
|
||||
mdiEmailOutline,
|
||||
mdiFormTextbox,
|
||||
} from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
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 IntakeLead = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
company?: string;
|
||||
role_title?: string;
|
||||
goal?: string;
|
||||
challenge?: string;
|
||||
desired_outcome?: string;
|
||||
status: string;
|
||||
consent_ai_notes?: boolean;
|
||||
};
|
||||
|
||||
function Panel({
|
||||
children,
|
||||
className = '',
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className={`rounded-lg border border-[#19192d]/10 bg-white ${className}`}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function IntakeLeads() {
|
||||
const [leads, setLeads] = React.useState<IntakeLead[]>([]);
|
||||
const [updatingLeadId, setUpdatingLeadId] = React.useState('');
|
||||
|
||||
async function loadLeads() {
|
||||
const response = await axios.get('/coaching/intake-leads');
|
||||
setLeads(response.data);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
loadLeads();
|
||||
}, []);
|
||||
|
||||
async function convertLead(leadId: string) {
|
||||
setUpdatingLeadId(leadId);
|
||||
await axios.post(`/coaching/intake-leads/${leadId}/convert`);
|
||||
await loadLeads();
|
||||
setUpdatingLeadId('');
|
||||
}
|
||||
|
||||
async function archiveLead(leadId: string) {
|
||||
setUpdatingLeadId(leadId);
|
||||
const response = await axios.patch(`/coaching/intake-leads/${leadId}/status`, {
|
||||
status: 'archived',
|
||||
});
|
||||
setLeads((current) =>
|
||||
current.map((lead) => {
|
||||
if (lead.id === leadId) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
return lead;
|
||||
}),
|
||||
);
|
||||
setUpdatingLeadId('');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Intake Leads')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<div className='mx-auto max-w-7xl'>
|
||||
<div className='mb-4 rounded-lg bg-[#19192d] p-5 text-white'>
|
||||
<div className='flex items-center gap-3 text-[#b17a1e]'>
|
||||
<BaseIcon path={mdiFormTextbox} size={18} />
|
||||
<span className='text-xs font-semibold uppercase tracking-[0.22em]'>
|
||||
Intake
|
||||
</span>
|
||||
</div>
|
||||
<h1 className='mt-3 text-xl font-semibold'>Intake leads</h1>
|
||||
<p className='mt-2 max-w-2xl text-sm leading-6 text-[#fffdf9]'>
|
||||
Review website submissions and convert good fits into client
|
||||
records.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-4'>
|
||||
{leads.map((lead) => (
|
||||
<Panel key={lead.id} className='p-5'>
|
||||
<div className='flex flex-col justify-between gap-4 lg:flex-row lg:items-start'>
|
||||
<div>
|
||||
<div className='flex flex-wrap items-center gap-3'>
|
||||
<h2 className='text-lg font-semibold text-[#19192d]'>
|
||||
{lead.name}
|
||||
</h2>
|
||||
<span className='rounded-full bg-[#fbf8f1] px-3 py-1 text-xs font-semibold text-[#b17a1e]'>
|
||||
{lead.status}
|
||||
</span>
|
||||
</div>
|
||||
<p className='mt-1 text-sm text-[#72798a]'>
|
||||
{lead.role_title || 'Role not provided'} ·{' '}
|
||||
{lead.company || 'Company not provided'}
|
||||
</p>
|
||||
<a
|
||||
href={`mailto:${lead.email}`}
|
||||
className='mt-3 inline-flex items-center gap-2 text-sm font-semibold text-[#35b7a5]'
|
||||
>
|
||||
<BaseIcon path={mdiEmailOutline} size={18} />
|
||||
{lead.email}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
{lead.status !== 'converted' && (
|
||||
<button
|
||||
type='button'
|
||||
disabled={updatingLeadId === lead.id}
|
||||
onClick={() => convertLead(lead.id)}
|
||||
className='inline-flex items-center gap-2 rounded-full bg-[#35b7a5] px-3 py-1.5 text-sm font-semibold text-white disabled:opacity-50'
|
||||
>
|
||||
<BaseIcon path={mdiAccountConvertOutline} size={18} />
|
||||
Convert to client
|
||||
</button>
|
||||
)}
|
||||
{lead.status !== 'archived' && (
|
||||
<button
|
||||
type='button'
|
||||
disabled={updatingLeadId === lead.id}
|
||||
onClick={() => archiveLead(lead.id)}
|
||||
className='inline-flex items-center gap-2 rounded-full border border-[#19192d]/10 bg-white px-3 py-1.5 text-sm font-semibold text-[#19192d] disabled:opacity-50'
|
||||
>
|
||||
<BaseIcon path={mdiArchiveOutline} size={18} />
|
||||
Archive
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-5 grid gap-4 lg:grid-cols-3'>
|
||||
<div className='rounded-lg bg-[#fffdf9] p-4'>
|
||||
<p className='text-xs font-semibold uppercase tracking-[0.18em] text-[#b17a1e]'>
|
||||
Goal
|
||||
</p>
|
||||
<p className='mt-3 text-sm leading-6 text-[#72798a]'>
|
||||
{lead.goal || 'No goal provided.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className='rounded-lg bg-[#fffdf9] p-4'>
|
||||
<p className='text-xs font-semibold uppercase tracking-[0.18em] text-[#b17a1e]'>
|
||||
Challenge
|
||||
</p>
|
||||
<p className='mt-3 text-sm leading-6 text-[#72798a]'>
|
||||
{lead.challenge || 'No challenge provided.'}
|
||||
</p>
|
||||
</div>
|
||||
<div className='rounded-lg bg-[#fffdf9] p-4'>
|
||||
<p className='text-xs font-semibold uppercase tracking-[0.18em] text-[#b17a1e]'>
|
||||
Desired outcome
|
||||
</p>
|
||||
<p className='mt-3 text-sm leading-6 text-[#72798a]'>
|
||||
{lead.desired_outcome || 'No outcome provided.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
))}
|
||||
|
||||
{leads.length === 0 && (
|
||||
<Panel className='p-5'>
|
||||
<p className='text-sm text-[#72798a]'>
|
||||
No intake leads yet. Share the public intake page from{' '}
|
||||
<Link href='/intake' className='font-semibold text-[#35b7a5]'>
|
||||
/intake
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</Panel>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
IntakeLeads.getLayout = function getLayout(page: ReactElement) {
|
||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||
};
|
||||
231
frontend/src/pages/intake.tsx
Normal file
231
frontend/src/pages/intake.tsx
Normal file
@ -0,0 +1,231 @@
|
||||
import axios from 'axios';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import LayoutGuest from '../layouts/Guest';
|
||||
import { getPageTitle } from '../config';
|
||||
|
||||
const fieldClass =
|
||||
'mt-2 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';
|
||||
|
||||
type IntakeValues = {
|
||||
name: string;
|
||||
email: string;
|
||||
company: string;
|
||||
role_title: string;
|
||||
goal: string;
|
||||
challenge: string;
|
||||
desired_outcome: string;
|
||||
consent_ai_notes: boolean;
|
||||
};
|
||||
|
||||
const emptyValues: IntakeValues = {
|
||||
name: '',
|
||||
email: '',
|
||||
company: '',
|
||||
role_title: '',
|
||||
goal: '',
|
||||
challenge: '',
|
||||
desired_outcome: '',
|
||||
consent_ai_notes: false,
|
||||
};
|
||||
|
||||
function FieldLabel({
|
||||
label,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<label className='block'>
|
||||
<span className='text-sm font-semibold text-[#72798a]'>{label}</span>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Intake() {
|
||||
const [values, setValues] = React.useState<IntakeValues>(emptyValues);
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
||||
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
||||
|
||||
function updateValue(field: keyof IntakeValues, value: string | boolean) {
|
||||
setValues((current) => {
|
||||
return {
|
||||
...current,
|
||||
[field]: value,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async function submitIntake(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
await axios.post('/coaching-public/intake', {
|
||||
...values,
|
||||
source: 'website',
|
||||
});
|
||||
setIsSubmitted(true);
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Start coaching')}</title>
|
||||
</Head>
|
||||
<main className='min-h-screen bg-[#fffdf9] text-[#19192d]'>
|
||||
<header className='px-5 py-5'>
|
||||
<div className='mx-auto flex max-w-6xl items-center justify-between'>
|
||||
<Link href='/' className='font-semibold'>
|
||||
Coaching SaaS Workspace
|
||||
</Link>
|
||||
<Link
|
||||
href='/login/'
|
||||
className='rounded-full border border-[#19192d]/10 bg-white px-4 py-2 text-sm font-semibold'
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section className='mx-auto grid max-w-6xl gap-8 px-5 py-10 lg:grid-cols-[0.85fr_1.15fr]'>
|
||||
<div>
|
||||
<p className='text-xs font-semibold uppercase tracking-[0.22em] text-[#b17a1e]'>
|
||||
Coaching intake
|
||||
</p>
|
||||
<h1 className='mt-4 font-serif text-5xl font-semibold leading-tight md:text-6xl'>
|
||||
Start with the coaching context that matters.
|
||||
</h1>
|
||||
<p className='mt-5 max-w-xl text-lg leading-8 text-[#72798a]'>
|
||||
Share what you want to work on. Your coach can review this,
|
||||
create a client record, and prepare the first session around your
|
||||
goals.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='rounded-lg border border-[#19192d]/10 bg-white p-5'>
|
||||
{isSubmitted ? (
|
||||
<div className='rounded-lg bg-[#f3fbf8] p-5'>
|
||||
<p className='text-xs font-semibold uppercase tracking-[0.22em] text-[#35b7a5]'>
|
||||
Received
|
||||
</p>
|
||||
<h2 className='mt-3 text-2xl font-semibold'>
|
||||
Thanks, we have your intake.
|
||||
</h2>
|
||||
<p className='mt-3 leading-7 text-[#72798a]'>
|
||||
Your coach can now review it and create your client workspace.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<form className='space-y-4' onSubmit={submitIntake}>
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<FieldLabel label='Name'>
|
||||
<input
|
||||
required
|
||||
value={values.name}
|
||||
onChange={(event) =>
|
||||
updateValue('name', event.target.value)
|
||||
}
|
||||
className={fieldClass}
|
||||
/>
|
||||
</FieldLabel>
|
||||
<FieldLabel label='Email'>
|
||||
<input
|
||||
required
|
||||
type='email'
|
||||
value={values.email}
|
||||
onChange={(event) =>
|
||||
updateValue('email', event.target.value)
|
||||
}
|
||||
className={fieldClass}
|
||||
/>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<FieldLabel label='Company'>
|
||||
<input
|
||||
value={values.company}
|
||||
onChange={(event) =>
|
||||
updateValue('company', event.target.value)
|
||||
}
|
||||
className={fieldClass}
|
||||
/>
|
||||
</FieldLabel>
|
||||
<FieldLabel label='Role'>
|
||||
<input
|
||||
value={values.role_title}
|
||||
onChange={(event) =>
|
||||
updateValue('role_title', event.target.value)
|
||||
}
|
||||
className={fieldClass}
|
||||
/>
|
||||
</FieldLabel>
|
||||
</div>
|
||||
|
||||
<FieldLabel label='What do you want coaching to help with?'>
|
||||
<textarea
|
||||
required
|
||||
rows={4}
|
||||
value={values.goal}
|
||||
onChange={(event) => updateValue('goal', event.target.value)}
|
||||
className={fieldClass}
|
||||
/>
|
||||
</FieldLabel>
|
||||
|
||||
<FieldLabel label='What feels stuck right now?'>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={values.challenge}
|
||||
onChange={(event) =>
|
||||
updateValue('challenge', event.target.value)
|
||||
}
|
||||
className={fieldClass}
|
||||
/>
|
||||
</FieldLabel>
|
||||
|
||||
<FieldLabel label='What would a useful first outcome look like?'>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={values.desired_outcome}
|
||||
onChange={(event) =>
|
||||
updateValue('desired_outcome', event.target.value)
|
||||
}
|
||||
className={fieldClass}
|
||||
/>
|
||||
</FieldLabel>
|
||||
|
||||
<label className='flex gap-3 rounded-lg bg-[#fbf8f1] p-4 text-sm leading-6 text-[#72798a]'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={values.consent_ai_notes}
|
||||
onChange={(event) =>
|
||||
updateValue('consent_ai_notes', event.target.checked)
|
||||
}
|
||||
className='mt-1'
|
||||
/>
|
||||
I am comfortable with AI-assisted summaries and prep notes
|
||||
being used by the coach inside this workspace.
|
||||
</label>
|
||||
|
||||
<button
|
||||
type='submit'
|
||||
disabled={isSubmitting}
|
||||
className='rounded-full bg-[#35b7a5] px-5 py-2.5 text-sm font-semibold text-white disabled:opacity-50'
|
||||
>
|
||||
{isSubmitting ? 'Submitting...' : 'Send intake'}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Intake.getLayout = function getLayout(page: React.ReactElement) {
|
||||
return <LayoutGuest>{page}</LayoutGuest>;
|
||||
};
|
||||
@ -84,7 +84,7 @@ function Nav() {
|
||||
<Link href='/#trust'>Trust</Link>
|
||||
</nav>
|
||||
<Link
|
||||
href='/register/'
|
||||
href='/intake/'
|
||||
className={`rounded-full px-5 py-2.5 text-sm font-semibold ${ui.button}`}
|
||||
>
|
||||
Start free
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user