diff --git a/apps/dashboard/src/components/auth/mobile-message.tsx b/apps/dashboard/src/components/auth/mobile-message.tsx deleted file mode 100644 index 14c9d3d0b74..00000000000 --- a/apps/dashboard/src/components/auth/mobile-message.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Smartphone } from 'lucide-react'; -import { useEffect } from 'react'; -import { post } from '@/api/api.client'; -import { showErrorToast } from '../primitives/sonner-helpers'; - -const MOBILE_WIDTH_THRESHOLD = 768; -const ONE_HOUR_MS = 60 * 60 * 1000; -const MOBILE_SETUP_STORAGE_KEY = 'mobileSetupEmailSentAt'; - -export function MobileMessage() { - useEffect(() => { - const notifyMobileSetup = async () => { - try { - const isMobile = window.innerWidth < MOBILE_WIDTH_THRESHOLD; - const lastSentAt = localStorage.getItem(MOBILE_SETUP_STORAGE_KEY); - - const now = Date.now(); - const shouldSendEmail = !lastSentAt || now - parseInt(lastSentAt) > ONE_HOUR_MS; - - if (isMobile && shouldSendEmail) { - localStorage.setItem(MOBILE_SETUP_STORAGE_KEY, now.toString()); - - await post('/support/mobile-setup', {}); - } - } catch (e) { - showErrorToast('Failed to send mobile setup email, please visit this page from Desktop.'); - } - }; - - notifyMobileSetup(); - }, []); - - return ( -
-
- -
-
-

Desktop Setup Required

-
-

đŸ‘‹ Hey, You're Almost There!

-

- We see you signed up from your mobile—nice move! But to complete the Novu setup, you'll need to switch over - to your laptop and fire up your favorite IDE. -

-

- Integrating Novu into your stack means writing some actual code, setting up workflows, configuring Inbox , - and composing your first email. -

-

- Check your inbox! We've sent you the setup instructions to get started. -

-
-
-
- ); -} diff --git a/apps/dashboard/src/components/auth/questionnaire-form.tsx b/apps/dashboard/src/components/auth/questionnaire-form.tsx deleted file mode 100644 index 8007b50df0f..00000000000 --- a/apps/dashboard/src/components/auth/questionnaire-form.tsx +++ /dev/null @@ -1,276 +0,0 @@ -import { useOrganization, useUser } from '@clerk/clerk-react'; -import { - CompanySizeEnum, - JobTitleEnum, - jobTitleToLabelMapper, - NewDashboardOptInStatusEnum, - OrganizationTypeEnum, -} from '@novu/shared'; -import { useMutation } from '@tanstack/react-query'; -import { AnimatePresence, motion } from 'motion/react'; -import React from 'react'; -import { Controller, useForm } from 'react-hook-form'; -import { useNavigate } from 'react-router-dom'; -import { updateClerkOrgMetadata } from '@/api/organization'; -import { identifyUser } from '@/api/telemetry'; -import { StepIndicator } from '@/components/auth/shared'; -import { Button } from '@/components/primitives/button'; -import { CardDescription, CardTitle } from '@/components/primitives/card'; -import { Form, FormRoot } from '@/components/primitives/form/form'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/primitives/select'; -import { useEnvironment, useFetchEnvironments } from '@/context/environment/hooks'; -import { useSegment } from '@/context/segment/hooks'; -import { useTelemetry } from '@/hooks/use-telemetry'; -import { ROUTES } from '@/utils/routes'; -import { TelemetryEvent } from '@/utils/telemetry'; - -interface QuestionnaireFormData { - jobTitle: JobTitleEnum; - organizationType: OrganizationTypeEnum; - companySize?: CompanySizeEnum; -} - -interface SubmitQuestionnaireData { - jobTitle: JobTitleEnum; - organizationType: OrganizationTypeEnum; - companySize?: CompanySizeEnum | string; - pageUri: string; - pageName: string; -} - -export function QuestionnaireForm() { - const { organization } = useOrganization(); - useFetchEnvironments({ organizationId: organization?.id }); - - const form = useForm(); - const { control, watch, handleSubmit } = form; - const submitQuestionnaireMutation = useSubmitQuestionnaire(); - const { user } = useUser(); - 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) => { - submitQuestionnaireMutation.mutate({ - ...data, - companySize: data.companySize || '1', - pageUri: window.location.href, - pageName: 'Create Organization Form', - }); - - // TODO: Make this more robust for all new sign-ups - if (!user?.unsafeMetadata?.newDashboardOptInStatus) { - await user?.update({ - unsafeMetadata: { - newDashboardOptInStatus: NewDashboardOptInStatusEnum.OPTED_IN, - }, - }); - // TODO: Reload shouldn't be necessary as user.update already returns the updated user - await user?.reload(); - } - }; - - return ( - <> -
-
-
-
-
- - - Help us personalize your experience - -
-
- - This helps us set up Novu to match your goals and plan features and improvements. - -
- -
- -
-
- - ( - - )} - /> -
- - - {selectedJobTitle && ( - - -
- ( - <> - {Object.values(OrganizationTypeEnum).map((type, index) => ( - - ))} - - )} - /> -
-
- )} - - {shouldShowCompanySize && ( - - -
- ( - <> - {Object.values(CompanySizeEnum).map((size, index) => ( - - ))} - - )} - /> -
-
- )} -
-
- - - {isFormValid && ( - - - - )} - -
-
-
-
- -
- create-org-illustration -
- - ); -} - -function useSubmitQuestionnaire() { - const segment = useSegment(); - const track = useTelemetry(); - const navigate = useNavigate(); - const { currentEnvironment } = useEnvironment(); - - return useMutation({ - mutationFn: async (data: SubmitQuestionnaireData) => { - await updateClerkOrgMetadata({ - environment: currentEnvironment!, - data: { - companySize: data.companySize, - jobTitle: data.jobTitle, - organizationType: data.organizationType, - }, - }); - - const anonymousId = await segment.getAnonymousId(); - - await identifyUser({ - pageUri: data.pageUri, - pageName: data.pageName, - jobTitle: data.jobTitle, - companySize: data.companySize, - organizationType: data.organizationType, - anonymousId, - }); - - track(TelemetryEvent.CREATE_ORGANIZATION_FORM_SUBMITTED, { - location: 'web', - jobTitle: data.jobTitle, - companySize: data.companySize, - organizationType: data.organizationType, - }); - }, - onSuccess: () => { - navigate(ROUTES.INBOX_USECASE); - }, - }); -} diff --git a/apps/dashboard/src/components/auth/shared.tsx b/apps/dashboard/src/components/auth/shared.tsx deleted file mode 100644 index 18a98b636c7..00000000000 --- a/apps/dashboard/src/components/auth/shared.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { RiArrowLeftSLine } from 'react-icons/ri'; -import { useNavigate } from 'react-router-dom'; -import { cn } from '../../utils/ui'; - -interface StepIndicatorProps { - step: number; - className?: string; - hideBackButton?: boolean; -} - -export function StepIndicator({ step, className, hideBackButton }: StepIndicatorProps) { - const navigate = useNavigate(); - - function handleGoBack() { - navigate(-1); - } - - return ( -
- {!hideBackButton && ( - - )} - {step}/3 -
- ); -} diff --git a/apps/dashboard/src/components/auth/usecase-selector.tsx b/apps/dashboard/src/components/auth/usecase-selector.tsx deleted file mode 100644 index 83479c664bc..00000000000 --- a/apps/dashboard/src/components/auth/usecase-selector.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { ChannelTypeEnum } from '@novu/shared'; -import { Card, CardContent } from '../primitives/card'; -import { StepIndicator } from './shared'; -import { Usecase } from './usecases-list.utils'; - -interface UsecaseSelectOnboardingProps { - onHover: (id: ChannelTypeEnum | null) => void; - onClick: (id: ChannelTypeEnum) => void; - selectedUseCases: ChannelTypeEnum[]; - channelOptions: Usecase[]; -} - -export function UsecaseSelectOnboarding({ - onHover, - onClick, - selectedUseCases, - channelOptions, -}: UsecaseSelectOnboardingProps) { - return ( -
-
-
- -
- -
-

How do you plan to use Novu?

-

- You can route notifications across channels intelligently with Novu's powerful workflows, among the channels - below. -

-
-
- -
- {channelOptions.map((option, index) => { - const isSelected = selectedUseCases.includes(option.id); - - return ( -
{ - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - onClick(option.id); - } - }} - onFocus={() => onHover(option.id)} - onBlur={() => onHover(null)} - > - onHover(option.id)} - onMouseLeave={() => onHover(null)} - onClick={() => onClick(option.id)} - > - -
-
- -
-
-

{option.title}

-

{option.description}

-
-
-
-
-
- ); - })} -
-
- ); -} diff --git a/apps/dashboard/src/components/auth/usecases-list.utils.tsx b/apps/dashboard/src/components/auth/usecases-list.utils.tsx deleted file mode 100644 index fdccb0e7029..00000000000 --- a/apps/dashboard/src/components/auth/usecases-list.utils.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { ChannelTypeEnum, StepTypeEnum } from '@novu/shared'; -import { IconType } from 'react-icons/lib'; -import { STEP_TYPE_TO_COLOR } from '../../utils/color'; -import { STEP_TYPE_TO_ICON } from '../icons/utils'; - -export interface Usecase { - icon: IconType; - title: string; - color: string; - id: ChannelTypeEnum; - description: string; - image: string; -} - -export const getChannelOptions = () => [ - { - icon: STEP_TYPE_TO_ICON[StepTypeEnum.IN_APP], - title: 'Inbox', - color: STEP_TYPE_TO_COLOR[StepTypeEnum.IN_APP], - id: ChannelTypeEnum.IN_APP, - description: 'Embed real-time in your product', - image: 'in_app-preview-v3.webp', - }, - { - icon: STEP_TYPE_TO_ICON[StepTypeEnum.EMAIL], - title: 'E-Mail', - color: STEP_TYPE_TO_COLOR[StepTypeEnum.EMAIL], - id: ChannelTypeEnum.EMAIL, - description: 'Sends Emails to your users', - image: 'email-preview.webp', - }, - { - icon: STEP_TYPE_TO_ICON[StepTypeEnum.SMS], - title: 'SMS', - color: STEP_TYPE_TO_COLOR[StepTypeEnum.SMS], - id: ChannelTypeEnum.SMS, - description: 'Sends SMS messages to your users', - image: 'sms-preview.webp', - }, - { - icon: STEP_TYPE_TO_ICON[StepTypeEnum.PUSH], - title: 'Push', - color: STEP_TYPE_TO_COLOR[StepTypeEnum.PUSH], - id: ChannelTypeEnum.PUSH, - description: 'Send push notifications to your users', - image: 'push-preview.webp', - }, - { - icon: STEP_TYPE_TO_ICON[StepTypeEnum.CHAT], - title: 'Chat', - color: STEP_TYPE_TO_COLOR[StepTypeEnum.CHAT], - id: ChannelTypeEnum.CHAT, - description: 'Send Slack and other chat notifications', - image: 'chat-preview.webp', - }, -];