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

[Admin]: Trigger Onboarding Flow for Open Graph without a Title #2392

Merged
merged 1 commit into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 31 additions & 0 deletions src/components/Auth/__tests__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,37 @@ describe('Auth Component', () => {
})
})

test('should show onboarding modal if admin and no title is set', async () => {
const [setBudget, setIsAdmin, setPubKey, setIsAuthenticated] = [jest.fn(), jest.fn(), jest.fn(), jest.fn()]

useUserStoreMock.mockReturnValue({
isAdmin: true,
setBudget,
setIsAdmin,
setPubKey,
setIsAuthenticated,
})

getIsAdminMock.mockResolvedValue({ data: { isAdmin: true, title: null } })
getSignedMessageFromRelayMock.mockResolvedValue({ message: 'testMessage', signature: 'testSignature' })

render(
<MemoryRouter>
<ThemeProvider theme={appTheme}>
<StyleThemeProvider theme={appTheme}>
<AuthGuard>
<App />
</AuthGuard>
</StyleThemeProvider>
</ThemeProvider>
</MemoryRouter>,
)

waitFor(() => {
expect(screen.getByText('Welcome to SecondBrain')).toBeInTheDocument()
})
})

test.skip('the unauthorized state is correctly set when the user lacks proper credentials', async () => {
const [setBudget, setIsAdmin, setPubKey, setIsAuthenticated] = [jest.fn(), jest.fn(), jest.fn(), jest.fn()]

Expand Down
7 changes: 7 additions & 0 deletions src/components/Auth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import styled from 'styled-components'
import { Flex } from '~/components/common/Flex'
import { Text } from '~/components/common/Text'
import { OnboardingModal } from '~/components/ModalsContainer/OnboardingFlow'
import { isDevelopment, isE2E } from '~/constants'
import { getIsAdmin } from '~/network/auth'
import { useDataStore } from '~/stores/useDataStore'
Expand All @@ -18,6 +19,7 @@
const { setBudget, setIsAdmin, setPubKey, setIsAuthenticated, setSwarmUiUrl } = useUserStore((s) => s)
const { splashDataLoading } = useDataStore((s) => s)
const [renderMainPage, setRenderMainPage] = useState(false)
const [showOnboarding, setShowOnboarding] = useState(false)

const {
setTrendingTopicsFeatureFlag,
Expand Down Expand Up @@ -75,6 +77,10 @@
setRealtimeGraphFeatureFlag(res.data.realtimeGraph || false)
setChatInterfaceFeatureFlag(res.data.chatInterface || false)
setFastFiltersFeatureFlag(res.data.fastFilters || false)

if (isAdmin && !res.data.title) {
setShowOnboarding(true)
}
}

setIsAuthenticated(true)
Expand Down Expand Up @@ -107,7 +113,7 @@

await handleAuth()
} catch (error) {
console.log(error)

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / eslint-run

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / craco-build-run

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/sourcesTable/sourcesTable.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/createGeneratedEdges/createGeneratedEdges.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addContent/addSource.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/admin/topics.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/curationTable/curation.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/checkEnv.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/admin/signin.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addContent/addTweet.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addContent/addYoutube.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addContent/addWebpage.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/trendingTopics/trendingTopics.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/addNode/addNodeType.cy.ts)

Unexpected console statement

Check warning on line 116 in src/components/Auth/index.tsx

View workflow job for this annotation

GitHub Actions / cypress-run (cypress/e2e/seeLatest/latest.cy.ts)

Unexpected console statement
}
}

Expand All @@ -131,6 +137,7 @@

return (
<>
{showOnboarding && <OnboardingModal />}
{splashDataLoading && <Splash />}
{renderMainPage && children}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
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<Props> = ({ 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 (
<Flex>
<Flex direction="column" justify="space-between">
<StyledText>Welcome to SecondBrain</StyledText>
<StyledSubText>Set a name and short description for your graph.</StyledSubText>
</Flex>

<StyledWrapper>
<Flex className="input__wrapper">
<TextInput
id="graph-title"
label="Title"
maxLength={50}
name="title"
placeholder="Type graph title here..."
rules={{
...requiredRule,
pattern: {
message: 'No leading whitespace allowed',
value: noSpacePattern,
},
}}
/>
<TextInput
id="graph-description"
label="Description"
maxLength={100}
name="description"
placeholder="Type graph description here..."
rules={{
...requiredRule,
pattern: {
message: 'No leading whitespace allowed',
value: noSpacePattern,
},
}}
/>
</Flex>
</StyledWrapper>

<Flex mt={10}>
<Button
color="secondary"
disabled={isSubmitting || !!error || !isFormValid}
onClick={onSubmit}
size="large"
variant="contained"
>
Confirm
</Button>
</Flex>
{error ? (
<StyledError>
<StyledErrorText>
<MdError className="errorIcon" />
<span>{error}</span>
</StyledErrorText>
</StyledError>
) : null}
</Flex>
)
}

const StyledText = styled(Text)`
font-size: 22px;
font-weight: 600;
font-family: 'Barlow';
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;
`
120 changes: 120 additions & 0 deletions src/components/ModalsContainer/OnboardingFlow/__tests__/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import '@testing-library/jest-dom'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import React from 'react'
import { postAboutData } from '~/network/fetchSourcesData'
import { useModal } from '~/stores/useModalStore'
import { OnboardingModal } from '../index'

jest.mock('~/network/fetchSourcesData', () => ({
postAboutData: jest.fn(),
}))

jest.mock('~/stores/useModalStore', () => ({
useModal: jest.fn(),
}))

const useModalMock = useModal as jest.MockedFunction<typeof useModal>
const postAboutDataMock = postAboutData as jest.MockedFunction<typeof postAboutData>

describe('OnboardingModal Component', () => {
beforeEach(() => {
jest.clearAllMocks()

useModalMock.mockReturnValue({
close: jest.fn(),
visible: true,
})
})

test('renders the onboarding modal', () => {
render(<OnboardingModal />)
expect(screen.getByText('Welcome to SecondBrain')).toBeInTheDocument()
expect(screen.getByText('Set a name and short description for your graph.')).toBeInTheDocument()
})

test('submits form successfully', async () => {
postAboutDataMock.mockResolvedValue({ status: 'success' })

render(<OnboardingModal />)

fireEvent.change(screen.getByPlaceholderText('Type graph title here...'), { target: { value: 'Test Title' } })

fireEvent.change(screen.getByPlaceholderText('Type graph description here...'), {
target: { value: 'Test Description' },
})

fireEvent.click(screen.getByText('Confirm'))

await waitFor(() => {
expect(postAboutDataMock).toHaveBeenCalledWith({
title: 'Test Title',
description: 'Test Description',
})
})
})

test('displays error on form submission failure', async () => {
postAboutDataMock.mockRejectedValue({ status: 400, json: async () => ({ errorCode: 'Error occurred' }) })

render(<OnboardingModal />)

fireEvent.change(screen.getByPlaceholderText('Type graph title here...'), { target: { value: 'Test Title' } })

fireEvent.change(screen.getByPlaceholderText('Type graph description here...'), {
target: { value: 'Test Description' },
})

fireEvent.click(screen.getByText('Confirm'))

await waitFor(() => {
expect(screen.getByText('Error occurred')).toBeInTheDocument()
})
})

test('closes modal on successful submission', async () => {
const closeMock = jest.fn()

useModalMock.mockReturnValue({
close: closeMock,
visible: true,
})

postAboutDataMock.mockResolvedValue({ status: 'success' })

render(<OnboardingModal />)

fireEvent.change(screen.getByPlaceholderText('Type graph title here...'), { target: { value: 'Test Title' } })

fireEvent.change(screen.getByPlaceholderText('Type graph description here...'), {
target: { value: 'Test Description' },
})

fireEvent.click(screen.getByText('Confirm'))

await waitFor(() => {
expect(closeMock).toHaveBeenCalled()
})
})

test('resets form and error on modal close', async () => {
const { rerender } = render(<OnboardingModal />)

fireEvent.change(screen.getByPlaceholderText('Type graph title here...'), { target: { value: 'Test Title' } })

fireEvent.change(screen.getByPlaceholderText('Type graph description here...'), {
target: { value: 'Test Description' },
})

useModalMock.mockReturnValue({
close: jest.fn(),
visible: false,
})

rerender(<OnboardingModal />)

waitFor(() => {
expect(screen.getByPlaceholderText('Type graph title here...')).toHaveValue('')
expect(screen.getByPlaceholderText('Type graph description here...')).toHaveValue('')
})
})
})
Loading
Loading