diff --git a/packages/pl-fe/src/actions/auth.ts b/packages/pl-fe/src/actions/auth.ts index 59b03f19b..95b9d1df5 100644 --- a/packages/pl-fe/src/actions/auth.ts +++ b/packages/pl-fe/src/actions/auth.ts @@ -21,7 +21,6 @@ import { createAccount } from 'pl-fe/actions/accounts'; import { createApp } from 'pl-fe/actions/apps'; import { fetchMeSuccess, fetchMeFail } from 'pl-fe/actions/me'; import { obtainOAuthToken, revokeOAuthToken } from 'pl-fe/actions/oauth'; -import { startOnboarding } from 'pl-fe/actions/onboarding'; import * as BuildConfig from 'pl-fe/build-config'; import { queryClient } from 'pl-fe/queries/client'; import { selectAccount } from 'pl-fe/selectors'; @@ -306,7 +305,6 @@ const register = (params: CreateAccountParams) => if ('identifier' in response) { toast.info(response.message); } else { - dispatch(startOnboarding()); return dispatch(authLoggedIn(response, app)); } }); diff --git a/packages/pl-fe/src/actions/onboarding.test.ts b/packages/pl-fe/src/actions/onboarding.test.ts deleted file mode 100644 index 5997266a5..000000000 --- a/packages/pl-fe/src/actions/onboarding.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { mockStore, mockWindowProperty, rootState } from 'pl-fe/jest/test-helpers'; - -import { checkOnboardingStatus, startOnboarding, endOnboarding } from './onboarding'; - -describe('checkOnboarding()', () => { - let mockGetItem: any; - - mockWindowProperty('localStorage', { - getItem: (key: string) => mockGetItem(key), - }); - - beforeEach(() => { - mockGetItem = vi.fn().mockReturnValue(null); - }); - - it('does nothing if localStorage item is not set', async() => { - mockGetItem = vi.fn().mockReturnValue(null); - - const state = { ...rootState }; - state.onboarding.needsOnboarding = false; - const store = mockStore(state); - - await store.dispatch(checkOnboardingStatus()); - const actions = store.getActions(); - - expect(actions).toEqual([]); - expect(mockGetItem.mock.calls.length).toBe(1); - }); - - it('does nothing if localStorage item is invalid', async() => { - mockGetItem = vi.fn().mockReturnValue('invalid'); - - const state = { ...rootState }; - state.onboarding.needsOnboarding = false; - const store = mockStore(state); - - await store.dispatch(checkOnboardingStatus()); - const actions = store.getActions(); - - expect(actions).toEqual([]); - expect(mockGetItem.mock.calls.length).toBe(1); - }); - - it('dispatches the correct action', async() => { - mockGetItem = vi.fn().mockReturnValue('1'); - - const state = { ...rootState }; - state.onboarding.needsOnboarding = false; - const store = mockStore(state); - - await store.dispatch(checkOnboardingStatus()); - const actions = store.getActions(); - - expect(actions).toEqual([{ type: 'ONBOARDING_START' }]); - expect(mockGetItem.mock.calls.length).toBe(1); - }); -}); - -describe('startOnboarding()', () => { - let mockSetItem: any; - - mockWindowProperty('localStorage', { - setItem: (key: string, value: string) => mockSetItem(key, value), - }); - - beforeEach(() => { - mockSetItem = vi.fn(); - }); - - it('dispatches the correct action', async() => { - const state = { ...rootState }; - state.onboarding.needsOnboarding = false; - const store = mockStore(state); - - await store.dispatch(startOnboarding()); - const actions = store.getActions(); - - expect(actions).toEqual([{ type: 'ONBOARDING_START' }]); - expect(mockSetItem.mock.calls.length).toBe(1); - }); -}); - -describe('endOnboarding()', () => { - let mockRemoveItem: any; - - mockWindowProperty('localStorage', { - removeItem: (key: string) => mockRemoveItem(key), - }); - - beforeEach(() => { - mockRemoveItem = vi.fn(); - }); - - it('dispatches the correct action', async() => { - const state = { ...rootState }; - state.onboarding.needsOnboarding = false; - const store = mockStore(state); - - await store.dispatch(endOnboarding()); - const actions = store.getActions(); - - expect(actions).toEqual([{ type: 'ONBOARDING_END' }]); - expect(mockRemoveItem.mock.calls.length).toBe(1); - }); -}); diff --git a/packages/pl-fe/src/actions/onboarding.ts b/packages/pl-fe/src/actions/onboarding.ts deleted file mode 100644 index a25b2137f..000000000 --- a/packages/pl-fe/src/actions/onboarding.ts +++ /dev/null @@ -1,41 +0,0 @@ -const ONBOARDING_START = 'ONBOARDING_START'; -const ONBOARDING_END = 'ONBOARDING_END'; - -const ONBOARDING_LOCAL_STORAGE_KEY = 'plfe:onboarding'; - -type OnboardingStartAction = { - type: typeof ONBOARDING_START; -} - -type OnboardingEndAction = { - type: typeof ONBOARDING_END; -} - -type OnboardingActions = OnboardingStartAction | OnboardingEndAction - -const checkOnboardingStatus = () => (dispatch: React.Dispatch) => { - const needsOnboarding = localStorage.getItem(ONBOARDING_LOCAL_STORAGE_KEY) === '1'; - - if (needsOnboarding) { - dispatch({ type: ONBOARDING_START }); - } -}; - -const startOnboarding = () => (dispatch: React.Dispatch) => { - localStorage.setItem(ONBOARDING_LOCAL_STORAGE_KEY, '1'); - dispatch({ type: ONBOARDING_START }); -}; - -const endOnboarding = () => (dispatch: React.Dispatch) => { - localStorage.removeItem(ONBOARDING_LOCAL_STORAGE_KEY); - dispatch({ type: ONBOARDING_END }); -}; - -export { - type OnboardingActions, - ONBOARDING_END, - ONBOARDING_START, - checkOnboardingStatus, - endOnboarding, - startOnboarding, -}; diff --git a/packages/pl-fe/src/features/onboarding/onboarding-wizard.tsx b/packages/pl-fe/src/features/onboarding/onboarding-wizard.tsx deleted file mode 100644 index 5483cb439..000000000 --- a/packages/pl-fe/src/features/onboarding/onboarding-wizard.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; -import ReactSwipeableViews from 'react-swipeable-views'; - -import { endOnboarding } from 'pl-fe/actions/onboarding'; -import LandingGradient from 'pl-fe/components/landing-gradient'; -import HStack from 'pl-fe/components/ui/hstack'; -import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; -import { useFeatures } from 'pl-fe/hooks/use-features'; -import { useSettings } from 'pl-fe/hooks/use-settings'; - -import AvatarSelectionStep from './steps/avatar-selection-step'; -import BioStep from './steps/bio-step'; -import CompletedStep from './steps/completed-step'; -import CoverPhotoSelectionStep from './steps/cover-photo-selection-step'; -import DisplayNameStep from './steps/display-name-step'; -import FediverseStep from './steps/fediverse-step'; -import SuggestedAccountsStep from './steps/suggested-accounts-step'; - -const OnboardingWizard = () => { - const dispatch = useAppDispatch(); - const features = useFeatures(); - const { theme } = useSettings(); - - const [currentStep, setCurrentStep] = React.useState(0); - - const handleSwipe = (nextStep: number) => { - setCurrentStep(nextStep); - }; - - const handlePreviousStep = () => { - setCurrentStep((prevStep) => Math.max(0, prevStep - 1)); - }; - - const handleNextStep = () => { - setCurrentStep((prevStep) => Math.min(prevStep + 1, steps.length - 1)); - }; - - const handleComplete = () => { - dispatch(endOnboarding()); - }; - - const steps = [ - , - , - , - , - , - ]; - - if (features.federating){ - steps.push(); - } - - steps.push(); - - const handleKeyUp = ({ key }: KeyboardEvent): void => { - switch (key) { - case 'ArrowLeft': - handlePreviousStep(); - break; - case 'ArrowRight': - handleNextStep(); - break; - } - }; - - const handleDotClick = (nextStep: number) => { - setCurrentStep(nextStep); - }; - - React.useEffect(() => { - document.addEventListener('keyup', handleKeyUp); - - return () => { - document.removeEventListener('keyup', handleKeyUp); - }; - }, []); - - return ( -
- {(theme?.backgroundGradient ?? true) && } - -
-
- - {steps.map((step, i) => ( -
-
- {step} -
-
- ))} -
- - - {steps.map((_, i) => ( -
-
-
- ); -}; - -export { OnboardingWizard as default }; diff --git a/packages/pl-fe/src/features/onboarding/steps/avatar-selection-step.tsx b/packages/pl-fe/src/features/onboarding/steps/avatar-selection-step.tsx deleted file mode 100644 index bb696818b..000000000 --- a/packages/pl-fe/src/features/onboarding/steps/avatar-selection-step.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import clsx from 'clsx'; -import React from 'react'; -import { defineMessages, FormattedMessage } from 'react-intl'; - -import { patchMe } from 'pl-fe/actions/me'; -import { BigCard } from 'pl-fe/components/big-card'; -import Avatar from 'pl-fe/components/ui/avatar'; -import Button from 'pl-fe/components/ui/button'; -import Icon from 'pl-fe/components/ui/icon'; -import Spinner from 'pl-fe/components/ui/spinner'; -import Stack from 'pl-fe/components/ui/stack'; -import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; -import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; -import toast from 'pl-fe/toast'; -import { isDefaultAvatar } from 'pl-fe/utils/accounts'; -import resizeImage from 'pl-fe/utils/resize-image'; - -import type { PlfeResponse } from 'pl-fe/api'; - -const messages = defineMessages({ - error: { id: 'onboarding.error', defaultMessage: 'An unexpected error occurred. Please try again or skip this step.' }, -}); - -const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => { - const dispatch = useAppDispatch(); - const { account } = useOwnAccount(); - - const fileInput = React.useRef(null); - const [selectedFile, setSelectedFile] = React.useState(); - const [isSubmitting, setSubmitting] = React.useState(false); - const [isDisabled, setDisabled] = React.useState(true); - const isDefault = account ? isDefaultAvatar(account.avatar) : false; - - const openFilePicker = () => { - fileInput.current?.click(); - }; - - const handleFileChange = (event: React.ChangeEvent) => { - const maxPixels = 400 * 400; - const rawFile = event.target.files?.item(0); - - if (!rawFile) return; - - resizeImage(rawFile, maxPixels).then((file) => { - const url = file ? URL.createObjectURL(file) : account?.avatar as string; - - setSelectedFile(url); - setSubmitting(true); - - const credentials = dispatch(patchMe({ avatar: rawFile })); - - return Promise.all([credentials]).then(() => { - setDisabled(false); - setSubmitting(false); - onNext(); - }).catch((error: { response: PlfeResponse }) => { - setSubmitting(false); - setDisabled(false); - setSelectedFile(null); - - if (error.response?.status === 422) { - toast.error(error.response.json.error.replace('Validation failed: ', '')); - } else { - toast.error(messages.error); - } - }); - }).catch(console.error); - }; - - return ( - } - subtitle={} - > - -
- {account && ( - - )} - - {isSubmitting && ( -
- -
- )} - - - - -
- - - - - {isDisabled && ( - - )} - -
-
- ); -}; - -export { AvatarSelectionStep as default }; diff --git a/packages/pl-fe/src/features/onboarding/steps/bio-step.tsx b/packages/pl-fe/src/features/onboarding/steps/bio-step.tsx deleted file mode 100644 index 7b87abffa..000000000 --- a/packages/pl-fe/src/features/onboarding/steps/bio-step.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; - -import { patchMe } from 'pl-fe/actions/me'; -import { BigCard } from 'pl-fe/components/big-card'; -import Button from 'pl-fe/components/ui/button'; -import FormGroup from 'pl-fe/components/ui/form-group'; -import Stack from 'pl-fe/components/ui/stack'; -import Textarea from 'pl-fe/components/ui/textarea'; -import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch'; -import { useOwnAccount } from 'pl-fe/hooks/use-own-account'; -import toast from 'pl-fe/toast'; - -import type { PlfeResponse } from 'pl-fe/api'; - -const messages = defineMessages({ - bioPlaceholder: { id: 'onboarding.bio.placeholder', defaultMessage: 'Tell the world a little about yourself…' }, - error: { id: 'onboarding.error', defaultMessage: 'An unexpected error occurred. Please try again or skip this step.' }, -}); - -const BioStep = ({ onNext }: { onNext: () => void }) => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - - const { account } = useOwnAccount(); - const [value, setValue] = React.useState(account?.__meta.source?.note ?? ''); - const [isSubmitting, setSubmitting] = React.useState(false); - const [errors, setErrors] = React.useState([]); - - const handleSubmit = () => { - setSubmitting(true); - - const credentials = dispatch(patchMe({ note: value })); - - Promise.all([credentials]) - .then(() => { - setSubmitting(false); - onNext(); - }).catch((error: { response: PlfeResponse }) => { - setSubmitting(false); - - if (error.response?.status === 422) { - setErrors([error.response.json.error.replace('Validation failed: ', '')]); - } else { - toast.error(messages.error); - } - }); - }; - - return ( - } - subtitle={} - > - -
- } - labelText={} - errors={errors} - > -