Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dashboard): Sign up Questionnaire #7114

Merged
merged 61 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
34d54ad
feat: update sign up and sign-in screens
scopsy Nov 19, 2024
8e377c0
feat: appereance
scopsy Nov 19, 2024
b8fc292
feat(dashboard): updated sign up page designs
scopsy Nov 19, 2024
63b6fb2
feat: add tooltip
scopsy Nov 19, 2024
94e6ba4
Update auth-feature-row.tsx
scopsy Nov 19, 2024
7454778
feat: att types
scopsy Nov 19, 2024
55f9768
Merge branch 'next' into new-signup-page
scopsy Nov 20, 2024
0ce1e49
feat: add create-org-card
scopsy Nov 20, 2024
09976b8
feat: add org create
scopsy Nov 20, 2024
686f485
Delete organization-name-preview.tsx
scopsy Nov 20, 2024
cbcb451
feat: add questionnaire screens
scopsy Nov 20, 2024
6165b9c
Merge branch 'next' into new-signup-page
scopsy Nov 20, 2024
c852a7e
fix: pr review comments
scopsy Nov 21, 2024
35080b1
fix: pr review
scopsy Nov 21, 2024
029878b
Update region-picker.tsx
scopsy Nov 21, 2024
e5f0c14
Merge branch 'next' into new-signup-page
scopsy Nov 21, 2024
b638f9b
Merge branch 'new-signup-page' into org-creation-flow
scopsy Nov 21, 2024
1a2dd63
fix: update svg
scopsy Nov 21, 2024
c2864fb
Update create-organization.tsx
scopsy Nov 21, 2024
8802d70
Merge branch 'org-creation-flow' into questionnaire-screen
scopsy Nov 21, 2024
7989706
Update create-organization.tsx
scopsy Nov 21, 2024
e3ef188
fix:
scopsy Nov 21, 2024
7427a8e
feat: add select
scopsy Nov 24, 2024
d00fa97
Merge branch 'next' into new-signup-page
scopsy Nov 24, 2024
4b72b5f
Merge branch 'new-signup-page' into org-creation-flow
scopsy Nov 24, 2024
dd5b364
Merge branch 'org-creation-flow' into questionnaire-screen
scopsy Nov 24, 2024
d75263a
feat: add jscookie
scopsy Nov 24, 2024
0e4ecb1
feat: usecase selector
scopsy Nov 24, 2024
5aed426
Update usecase-selector.tsx
scopsy Nov 24, 2024
d88ecdd
Update questionnaire-form.tsx
scopsy Nov 24, 2024
6bb2c11
feat: optimize
scopsy Nov 24, 2024
c41affa
Update tailwind.config.js
scopsy Nov 24, 2024
a0964da
feat: refactor
scopsy Nov 24, 2024
13e19cf
simplify div structure
scopsy Nov 24, 2024
bd6b524
feat: add telemtry
scopsy Nov 24, 2024
95b4460
feat: inbox
scopsy Nov 24, 2024
bb0a5f7
Update .cspell.json
scopsy Nov 24, 2024
d450deb
Merge branch 'next' into org-creation-flow
scopsy Nov 24, 2024
ecb4626
Update questionnaire-form.tsx
scopsy Nov 24, 2024
0cc5bd1
Merge branch 'next' into org-creation-flow
scopsy Nov 25, 2024
3882383
Merge branch 'org-creation-flow' into questionnaire-screen
scopsy Nov 25, 2024
a396053
fix: preview
scopsy Nov 25, 2024
99b0a9b
Update index.ts
scopsy Nov 25, 2024
4997001
fix: gradient
scopsy Nov 25, 2024
93473c2
fix: pr review
scopsy Nov 26, 2024
abfa1fd
Update usecase-selector.tsx
scopsy Nov 26, 2024
0606ec4
feat: form refactoring
scopsy Nov 26, 2024
cb0b2f3
feat: refactor questionnaire
scopsy Nov 26, 2024
4694e7e
fix: colors
scopsy Nov 26, 2024
b50c0f3
Merge branch 'next' into org-creation-flow
scopsy Nov 26, 2024
3014085
Merge branch 'org-creation-flow' into questionnaire-screen
scopsy Nov 26, 2024
49327e6
feat:
scopsy Nov 26, 2024
5a415ce
Update organization-list.tsx
scopsy Nov 26, 2024
1046bff
Merge branch 'org-creation-flow' into questionnaire-screen
scopsy Nov 26, 2024
ebc66d1
Merge branch 'next' into questionnaire-screen
scopsy Nov 26, 2024
c26e44a
Update questionnaire-form.tsx
scopsy Nov 26, 2024
9f51231
Update auth-layout.tsx
scopsy Nov 26, 2024
39cfd79
feat: update photos
scopsy Nov 26, 2024
e6bd453
Merge branch 'next' into questionnaire-screen
scopsy Nov 26, 2024
3500b5e
Merge branch 'next' into questionnaire-screen
scopsy Nov 27, 2024
ae6c40d
Merge branch 'next' into questionnaire-screen
scopsy Nov 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,7 @@
"xyflow",
"zulip",
"zwnj",
"lstrip",
"SOLOPRENEUR",
"rstrip",
"truncatewords",
"xmlschema",
Expand Down
2 changes: 2 additions & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"@segment/analytics-next": "^1.73.0",
"@sentry/react": "^8.35.0",
"@tanstack/react-query": "^5.59.6",
"@types/js-cookie": "^3.0.6",
"@uiw/codemirror-extensions-langs": "^4.23.6",
"@uiw/codemirror-theme-white": "^4.23.6",
"@uiw/codemirror-themes": "^4.23.6",
Expand All @@ -64,6 +65,7 @@
"date-fns": "^4.1.0",
"flat": "^6.0.1",
"framer-motion": "^11.3.19",
"js-cookie": "^3.0.5",
"lodash.debounce": "^4.0.8",
"lodash.merge": "^4.6.2",
"lucide-react": "^0.439.0",
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
6 changes: 6 additions & 0 deletions apps/dashboard/src/api/organization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { UpdateExternalOrganizationDto } from '@novu/shared';
import { post } from './api.client';

export function updateClerkOrgMetadata(data: UpdateExternalOrganizationDto) {
return post('/clerk/organization', data);
}
20 changes: 20 additions & 0 deletions apps/dashboard/src/api/telemetry.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import { OrganizationTypeEnum, CompanySizeEnum, JobTitleEnum } from '@novu/shared';
import { post } from './api.client';
import * as Sentry from '@sentry/react';

export const sendTelemetry = async (event: string, data?: Record<string, unknown>): Promise<void> => {
await post('/telemetry/measure', {
event,
data,
});
};

interface IdentifyUserProps {
hubspotContext: string;
pageUri: string;
pageName: string;
jobTitle: JobTitleEnum;
organizationType: OrganizationTypeEnum;
companySize?: CompanySizeEnum;
}

export const identifyUser = async (userData: IdentifyUserProps) => {
try {
await post('/telemetry/identify', userData);
} catch (error) {
console.error('Error identifying user:', error);
scopsy marked this conversation as resolved.
Show resolved Hide resolved
Sentry.captureException(error);
}
};
2 changes: 1 addition & 1 deletion apps/dashboard/src/components/auth-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReactNode } from 'react';

export const AuthLayout = ({ children }: { children: ReactNode }) => {
return (
<div className="flex h-screen items-center justify-center gap-8 bg-[url('/images/auth/background.svg')]">
<div className="flex h-screen items-center justify-center gap-8 bg-[url('/images/auth/background.svg')] bg-cover bg-no-repeat">
<div className="flex max-w-[1100px] flex-1 flex-row">{children}</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/components/auth/auth-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Card } from '../primitives/card';

export function AuthCard({ children }: { children: React.ReactNode }) {
return <Card className="flex h-[692px] w-full overflow-hidden">{children}</Card>;
return <Card className="flex min-h-[692px] w-full overflow-hidden">{children}</Card>;
}
18 changes: 5 additions & 13 deletions apps/dashboard/src/components/auth/create-organization.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { OrganizationList as OrganizationListForm } from '@clerk/clerk-react';
import { ROUTES } from '../../utils/routes';
import { clerkSignupAppearance } from '../../utils/clerk-appearance';
import { AuthCard } from './auth-card';
import { RiArrowLeftSLine } from 'react-icons/ri';
import { StepIndicator } from './shared';

export default function OrganizationCreate() {
return (
<div className="mx-auto flex w-full max-w-[1130px] flex-col gap-3">
<AuthCard>
<div className="flex min-w-[564px] max-w-[564px] items-center p-[60px]">
<div className="flex flex-col gap-[4px]">
<StepIndicator />
<StepIndicator hideBackButton className="pl-[20px]" step={1} />

<OrganizationListForm
appearance={{
elements: {
Expand All @@ -22,24 +23,15 @@ export default function OrganizationCreate() {
hidePersonal
skipInvitationScreen
afterSelectOrganizationUrl={ROUTES.ENV}
afterCreateOrganizationUrl={ROUTES.ENV}
afterCreateOrganizationUrl={ROUTES.SIGNUP_QUESTIONNAIRE}
/>
</div>
</div>

<div className="w-full max-w-[564px] flex-1">
<img src="/images/auth/ui-org.svg" alt="create-org-illustration" className="opacity-70" />
<img src="/images/auth/ui-org.svg" alt="Novu dashboard overview" className="opacity-70" />
</div>
</AuthCard>
</div>
);
}

function StepIndicator(): JSX.Element {
return (
<div className="text-foreground-600 inline-flex items-center gap-[2px] pl-[20px]">
<RiArrowLeftSLine className="h-4 w-4" />
<span className="font-label-x-small text-xs">1/3</span>
</div>
);
}
217 changes: 217 additions & 0 deletions apps/dashboard/src/components/auth/questionnaire-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import { Button } from '@/components/primitives/button';
import { CardDescription, CardTitle } from '@/components/primitives/card';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/primitives/select';
import React from 'react';
import { StepIndicator } from './shared';
import { JobTitleEnum, jobTitleToLabelMapper, OrganizationTypeEnum, CompanySizeEnum } from '@novu/shared';
import { useForm, Controller } from 'react-hook-form';
import { updateClerkOrgMetadata } from '../../api/organization';
import { hubspotCookie } from '../../utils/cookies';
import { identifyUser } from '../../api/telemetry';
import { useTelemetry } from '../../hooks';
import { TelemetryEvent } from '../../utils/telemetry';
import { useNavigate } from 'react-router-dom';
import { ROUTES } from '../../utils/routes';
import { useMutation } from '@tanstack/react-query';

interface QuestionnaireFormData {
jobTitle: JobTitleEnum;
organizationType: OrganizationTypeEnum;
companySize?: CompanySizeEnum;
}

interface SubmitQuestionnaireData {
jobTitle: JobTitleEnum;
organizationType: OrganizationTypeEnum;
companySize?: CompanySizeEnum;
pageUri: string;
pageName: string;
hubspotContext: string;
}

export function QuestionnaireForm() {
const { control, watch, handleSubmit } = useForm<QuestionnaireFormData>();
const submitQuestionnaireMutation = useSubmitQuestionnaire();

const selectedJobTitle = watch('jobTitle');
const selectedOrgType = watch('organizationType');
const companySize = watch('companySize');

const shouldShowCompanySize =
(selectedOrgType === OrganizationTypeEnum.COMPANY || selectedOrgType === OrganizationTypeEnum.AGENCY) &&
!!selectedJobTitle;

const isFormValid = React.useMemo(() => {
if (!selectedJobTitle || !selectedOrgType) return false;
if (shouldShowCompanySize && !companySize) return false;

return true;
}, [selectedJobTitle, selectedOrgType, shouldShowCompanySize, companySize]);

const onSubmit = async (data: QuestionnaireFormData) => {
const hubspotContext = hubspotCookie.get();

submitQuestionnaireMutation.mutate({
...data,
pageUri: window.location.href,
pageName: 'Create Organization Form',
hubspotContext: hubspotContext || '',
});
};

return (
<>
<div className="w-full max-w-[564px] px-0 pt-[80px]">
<div className="flex flex-col items-center gap-8">
<div className="flex w-[350px] flex-col gap-1">
<div className="flex w-full items-center gap-1.5">
<div className="flex flex-1 flex-col gap-1">
<StepIndicator step={2} />
scopsy marked this conversation as resolved.
Show resolved Hide resolved
<CardTitle className="text-foreground-900 text-lg font-medium">
Help us personalize your experience
</CardTitle>
</div>
</div>
<CardDescription className="text-foreground-400 text-xs">
This helps us set up Novu to match your goals and plan features and improvements.
</CardDescription>
</div>

<form onSubmit={handleSubmit(onSubmit)} className="flex w-[350px] flex-col gap-8">
<div className="flex flex-col gap-7">
<div className="flex flex-col gap-[4px]">
<label className="text-foreground-600 text-xs font-medium">Job title</label>
<Controller
name="jobTitle"
control={control}
render={({ field }) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger
className={`shadow-regular-shadow-x-small h-[32px] w-full border border-[#E1E4EA] ${field.value ? 'text-[#0E121B]' : 'text-[#99A0AE]'}`}
>
<SelectValue placeholder="What's your nature of work" />
</SelectTrigger>
<SelectContent>
{Object.entries(jobTitleToLabelMapper).map(([value, label]) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
</div>

{selectedJobTitle && (
<div className="flex flex-col gap-[4px]">
<label className="text-xs font-medium text-[#525866]">Organization type</label>
<div className="flex flex-wrap gap-[8px]">
<Controller
name="organizationType"
control={control}
render={({ field }) => (
<>
{Object.values(OrganizationTypeEnum).map((type) => (
<Button
key={type}
variant="outline"
size="xs"
type="button"
className={`h-[28px] rounded-full px-3 py-1 text-sm ${
field.value === type ? 'border-[#E1E4EA] bg-[#F2F5F8]' : 'border-[#E1E4EA]'
}`}
onClick={() => field.onChange(type)}
>
{type}
</Button>
))}
</>
)}
/>
</div>
</div>
)}

{shouldShowCompanySize && (
<div className="flex flex-col gap-[4px]">
<label className="text-xs font-medium text-[#525866]">Company size</label>
<div className="flex flex-wrap gap-[8px]">
<Controller
name="companySize"
control={control}
render={({ field }) => (
<>
{Object.values(CompanySizeEnum).map((size) => (
<Button
key={size}
variant="outline"
size="xs"
type="button"
className={`h-[28px] rounded-full px-3 py-1 text-sm ${
field.value === size ? 'border-[#E1E4EA] bg-[#F2F5F8]' : 'border-[#E1E4EA]'
}`}
onClick={() => field.onChange(size)}
>
{size}
</Button>
))}
</>
)}
/>
</div>
</div>
)}
</div>

{isFormValid && (
<div className="flex flex-col gap-3">
<Button className="bg-black" type="submit" disabled={submitQuestionnaireMutation.isPending}>
{submitQuestionnaireMutation.isPending ? 'Creating...' : 'Get started'}
</Button>
</div>
)}
</form>
</div>
</div>

<div className="w-full max-w-[564px] flex-1">
<img src="/images/auth/ui-org.svg" alt="create-org-illustration" />
</div>
</>
);
}

function useSubmitQuestionnaire() {
const track = useTelemetry();
const navigate = useNavigate();

return useMutation({
mutationFn: async (data: SubmitQuestionnaireData) => {
await updateClerkOrgMetadata({
companySize: data.companySize,
jobTitle: data.jobTitle,
organizationType: data.organizationType,
});

await identifyUser({
jobTitle: data.jobTitle,
pageUri: data.pageUri,
pageName: data.pageName,
hubspotContext: data.hubspotContext,
companySize: data.companySize,
organizationType: data.organizationType,
});

track(TelemetryEvent.CREATE_ORGANIZATION_FORM_SUBMITTED, {
location: 'web',
jobTitle: data.jobTitle,
companySize: data.companySize,
organizationType: data.organizationType,
});
},
onSuccess: () => {
navigate(ROUTES.USECASE_SELECT);
},
});
}
33 changes: 33 additions & 0 deletions apps/dashboard/src/components/auth/shared.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { RiArrowLeftSLine } from 'react-icons/ri';
import { cn } from '../../utils/ui';
import { useNavigate } from 'react-router-dom';

interface StepIndicatorProps {
step: number;
className?: string;
hideBackButton?: boolean;
}

export function StepIndicator({ step, className, hideBackButton }: StepIndicatorProps) {
const navigate = useNavigate();

function handleGoBack() {
navigate(-1);
}

return (
<div className={cn('text-foreground-600 inline-flex items-center gap-0.5', className)}>
{!hideBackButton && (
<button
onClick={handleGoBack}
className="transition-colors hover:text-gray-700"
type="button"
aria-label="Go back to previous step"
>
<RiArrowLeftSLine className="h-4 w-4" />
</button>
)}
<span className="font-label-x-small text-xs">{step}/3</span>
</div>
);
}
Loading
Loading