From d0105fd9d33a824847c099ec6c865f9bbf0a32ef Mon Sep 17 00:00:00 2001 From: aliraza556 Date: Sat, 26 Oct 2024 13:21:05 +0500 Subject: [PATCH] fix(onboarding-flow): implement onboarding functionality --- src/components/App/Splash/index.tsx | 10 ++ .../OnboardingFlow/GraphDetailsStep/index.tsx | 165 ++++++++++++++++++ .../ModalsContainer/OnboardingFlow/index.tsx | 71 ++++++++ src/components/ModalsContainer/index.tsx | 5 + src/stores/useModalStore/index.ts | 2 + 5 files changed, 253 insertions(+) create mode 100644 src/components/ModalsContainer/OnboardingFlow/GraphDetailsStep/index.tsx create mode 100644 src/components/ModalsContainer/OnboardingFlow/index.tsx diff --git a/src/components/App/Splash/index.tsx b/src/components/App/Splash/index.tsx index 5bd2a4209..2322938de 100644 --- a/src/components/App/Splash/index.tsx +++ b/src/components/App/Splash/index.tsx @@ -7,7 +7,9 @@ import { Text } from '~/components/common/Text' import { getAboutData, getSchemaAll, getStats } from '~/network/fetchSourcesData' import { useAppStore } from '~/stores/useAppStore' import { useDataStore } from '~/stores/useDataStore' +import { useModal } from '~/stores/useModalStore' import { useSchemaStore } from '~/stores/useSchemaStore' +import { useUserStore } from '~/stores/useUserStore' import { colors, formatSplashMessage, formatStatsResponse } from '~/utils' import { AnimatedTextContent } from './animated' import { initialMessageData, Message } from './constants' @@ -19,6 +21,8 @@ export const Splash = () => { const { appMetaData, setAppMetaData } = useAppStore((s) => s) const { stats, setStats, setSeedQuestions } = useDataStore((s) => s) const { schemas, setSchemas } = useSchemaStore((s) => s) + const { isAdmin } = useUserStore((s) => s) + const { open, visible } = useModal('onboardingFlow') const [isLoading, setIsLoading] = useState(false) // Individual useEffect hooks for each data requirement @@ -63,6 +67,12 @@ export const Splash = () => { } }, [appMetaData, isLoading, schemas.length, setAppMetaData, setSchemas, setSeedQuestions, setStats, stats]) + useEffect(() => { + if (!isLoading && isAdmin && !appMetaData?.title && !visible) { + open() + } + }, [isLoading, isAdmin, appMetaData?.title, open, visible]) + useEffect(() => { const fetchSchemas = async () => { try { diff --git a/src/components/ModalsContainer/OnboardingFlow/GraphDetailsStep/index.tsx b/src/components/ModalsContainer/OnboardingFlow/GraphDetailsStep/index.tsx new file mode 100644 index 000000000..5331ec103 --- /dev/null +++ b/src/components/ModalsContainer/OnboardingFlow/GraphDetailsStep/index.tsx @@ -0,0 +1,165 @@ +import { Button } from '@mui/material' +import { FC, useEffect } from 'react' +import { useFormContext } from 'react-hook-form' +import { MdError } from 'react-icons/md' +import styled from 'styled-components' +import { noSpacePattern } from '~/components/AddItemModal/SourceTypeStep/constants' +import { Flex } from '~/components/common/Flex' +import { Text } from '~/components/common/Text' +import { TextInput } from '~/components/common/TextInput' +import { requiredRule } from '~/constants' +import { colors } from '~/utils' + +type Props = { + onSubmit: () => void + error?: string +} + +export const GraphDetailsStep: FC = ({ onSubmit, error }) => { + const { + formState: { isSubmitting }, + watch, + } = useFormContext() + + const title = watch('title') + const description = watch('description') + + const isFormValid = !!title?.trim() && !!description?.trim() + + useEffect(() => { + const titleInput = document.getElementById('graph-title') as HTMLInputElement + + if (titleInput) { + titleInput.focus() + } + }, []) + + return ( + + + Welcome to SecondBrain! + Set a name and short description for your graph. + + + + + + + + + + + + + {error ? ( + + + + {error} + + + ) : null} + + ) +} + +const StyledText = styled(Text)` + font-size: 22px; + font-weight: 600; + font-family: 'Barlow'; + text-align: center; + margin-bottom: 10px; +` + +const StyledSubText = styled(Text)` + font-size: 14px; + font-family: 'Barlow'; + margin-bottom: 20px; +` + +const StyledWrapper = styled(Flex)` + width: 100%; + display: flex; + justify-content: center; + gap: 10px; + margin: 0 0 15px 0; + + .input__wrapper { + display: flex; + gap: 23px; + max-height: 225px; + overflow-y: auto; + padding-right: 20px; + width: calc(100% + 20px); + } +` + +const StyledErrorText = styled(Flex)` + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 2px; + + .errorIcon { + display: block; + font-size: 13px; + min-height: 13px; + min-width: 13px; + } + + span { + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + overflow: hidden; + white-space: normal; + letter-spacing: 0.2px; + padding-left: 4px; + font-size: 13px; + font-family: Barlow; + line-height: 18px; + } +` + +const StyledError = styled(Flex)` + display: flex; + align-items: center; + color: ${colors.primaryRed}; + position: relative; + margin-top: 20px; +` diff --git a/src/components/ModalsContainer/OnboardingFlow/index.tsx b/src/components/ModalsContainer/OnboardingFlow/index.tsx new file mode 100644 index 000000000..7df0141c7 --- /dev/null +++ b/src/components/ModalsContainer/OnboardingFlow/index.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import { SuccessNotify } from '~/components/common/SuccessToast' +import { BaseModal } from '~/components/Modal' +import { NODE_ADD_ERROR } from '~/constants' +import { postAboutData, TAboutParams } from '~/network/fetchSourcesData' +import { useModal } from '~/stores/useModalStore' +import { GraphDetailsStep } from './GraphDetailsStep' + +export type FormData = { + title: string + description: string +} + +export const OnboardingModal = () => { + const { close, visible } = useModal('onboardingFlow') + const form = useForm({ mode: 'onChange' }) + const { reset } = form + const [error, setError] = useState('') + + useEffect(() => { + if (!visible) { + reset() + setError('') + } + }, [visible, reset]) + + const submitGraphDetails = async (data: TAboutParams, onSuccess: () => void, onError: (error: string) => void) => { + try { + const res = (await postAboutData(data)) as Awaited<{ status: string }> + + if (res.status === 'success') { + SuccessNotify('Graph details saved') + onSuccess() + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + let errorMessage = NODE_ADD_ERROR + + if (err?.status === 400) { + const errorRes = await err.json() + + errorMessage = errorRes.errorCode || errorRes?.status || NODE_ADD_ERROR + } else if (err instanceof Error) { + errorMessage = err.message + } + + onError(String(errorMessage)) + } + } + + const onSubmit = form.handleSubmit(async (data) => { + await submitGraphDetails( + data, + () => { + close() + }, + setError, + ) + }) + + return ( + + +
+ + +
+
+ ) +} diff --git a/src/components/ModalsContainer/index.tsx b/src/components/ModalsContainer/index.tsx index 664489b77..c40fd3a60 100644 --- a/src/components/ModalsContainer/index.tsx +++ b/src/components/ModalsContainer/index.tsx @@ -46,6 +46,10 @@ const LazyCreateBountyModal = lazy(() => import('./CreateBountyModal').then(({ CreateBountyModal }) => ({ default: CreateBountyModal })), ) +const LazyOnboardingModal = lazy(() => + import('./OnboardingFlow').then(({ OnboardingModal }) => ({ default: OnboardingModal })), +) + export const ModalsContainer = () => ( <> @@ -60,5 +64,6 @@ export const ModalsContainer = () => ( + ) diff --git a/src/stores/useModalStore/index.ts b/src/stores/useModalStore/index.ts index 5dbf17182..b9efb3681 100644 --- a/src/stores/useModalStore/index.ts +++ b/src/stores/useModalStore/index.ts @@ -21,6 +21,7 @@ export type AvailableModals = | 'changeNodeType' | 'feedback' | 'createBounty' + | 'onboardingFlow' type ModalStore = { currentModals: Record @@ -51,6 +52,7 @@ const defaultData = { changeNodeType: false, feedback: false, createBounty: false, + onboardingFlow: false, }, }