mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-10-21 16:44:34 -04:00
[ENG-1007] Per-page onboarding forms (#1256)
* useMultiZodForm * fix imports * handle obStore.data undefined --------- Co-authored-by: Utku <74243531+utkubakir@users.noreply.github.com>
This commit is contained in:
1 parent
08ba4f917a
commit
795bb18d18
33 files changed
+361
-264
No files matched your search
@@ -1,6 +1,5 @@
|
||||
import { useLibraryMutation } from '@sd/client';
|
||||
import { useLibraryMutation, useZodForm } from '@sd/client';
|
||||
import { CheckBox, Dialog, Tooltip, UseDialogProps, useDialog } from '@sd/ui';
|
||||
import { useZodForm } from '@sd/ui/src/forms';
|
||||
|
||||
interface Props extends UseDialogProps {
|
||||
locationId: number;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { FilePath, useLibraryMutation } from '@sd/client';
|
||||
import { Dialog, Slider, UseDialogProps, useDialog } from '@sd/ui';
|
||||
import { useZodForm, z } from '@sd/ui/src/forms';
|
||||
import { FilePath, useLibraryMutation, useZodForm } from '@sd/client';
|
||||
import { Dialog, Slider, UseDialogProps, useDialog, z } from '@sd/ui';
|
||||
|
||||
interface Props extends UseDialogProps {
|
||||
locationId: number;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
import { Dialog, TextArea, UseDialogProps, useDialog } from '@sd/ui';
|
||||
import { useZodForm, z } from '@sd/ui/src/forms';
|
||||
import { useZodForm } from '@sd/client';
|
||||
import { Dialog, TextArea, UseDialogProps, useDialog, z } from '@sd/ui';
|
||||
import { showAlertDialog } from '~/components';
|
||||
|
||||
const schema = z.object({
|
||||
|
||||
@@ -2,8 +2,8 @@ import clsx from 'clsx';
|
||||
import { useMotionValueEvent, useScroll } from 'framer-motion';
|
||||
import { CheckCircle } from 'phosphor-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Themes, getThemeStore, useThemeStore } from '@sd/client';
|
||||
import { Button, Form, SwitchField, useZodForm, z } from '@sd/ui';
|
||||
import { Themes, getThemeStore, useThemeStore, useZodForm } from '@sd/client';
|
||||
import { Button, Form, SwitchField, z } from '@sd/ui';
|
||||
import { usePlatform } from '~/util/Platform';
|
||||
import { Heading } from '../Layout';
|
||||
import Setting from '../Setting';
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { Laptop } from '@sd/assets/icons';
|
||||
import { getDebugState, useBridgeMutation, useBridgeQuery, useDebugState } from '@sd/client';
|
||||
import { Button, Card, Input, Switch, tw } from '@sd/ui';
|
||||
import { useZodForm, z } from '@sd/ui/src/forms';
|
||||
import {
|
||||
getDebugState,
|
||||
useBridgeMutation,
|
||||
useBridgeQuery,
|
||||
useDebugState,
|
||||
useZodForm
|
||||
} from '@sd/client';
|
||||
import { Button, Card, Input, Switch, tw, z } from '@sd/ui';
|
||||
import { useDebouncedFormWatch } from '~/hooks';
|
||||
import { usePlatform } from '~/util/Platform';
|
||||
import { Heading } from '../Layout';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { MaybeUndefined, useBridgeMutation, useLibraryContext } from '@sd/client';
|
||||
import { Button, Input, Switch, Tooltip, dialogManager } from '@sd/ui';
|
||||
import { useZodForm, z } from '@sd/ui/src/forms';
|
||||
import { MaybeUndefined, useBridgeMutation, useLibraryContext, useZodForm } from '@sd/client';
|
||||
import { Button, Input, Switch, Tooltip, dialogManager, z } from '@sd/ui';
|
||||
import { useDebouncedFormWatch } from '~/hooks';
|
||||
import { Heading } from '../Layout';
|
||||
import Setting from '../Setting';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Archive, ArrowsClockwise, Info, Trash } from 'phosphor-react';
|
||||
import { Suspense } from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { useLibraryMutation, useLibraryQuery } from '@sd/client';
|
||||
import { useLibraryMutation, useLibraryQuery, useZodForm } from '@sd/client';
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
SwitchField,
|
||||
Tooltip,
|
||||
tw,
|
||||
useZodForm,
|
||||
z
|
||||
} from '@sd/ui';
|
||||
import ModalLayout from '~/app/$libraryId/settings/ModalLayout';
|
||||
|
||||
@@ -8,9 +8,10 @@ import {
|
||||
extractInfoRSPCError,
|
||||
useLibraryMutation,
|
||||
useLibraryQuery,
|
||||
usePlausibleEvent
|
||||
usePlausibleEvent,
|
||||
useZodForm
|
||||
} from '@sd/client';
|
||||
import { Dialog, ErrorMessage, InputField, UseDialogProps, useDialog, useZodForm, z } from '@sd/ui';
|
||||
import { Dialog, ErrorMessage, InputField, UseDialogProps, useDialog, z } from '@sd/ui';
|
||||
import { showAlertDialog } from '~/components';
|
||||
import { useCallbackToWatchForm } from '~/hooks';
|
||||
import { Platform, usePlatform } from '~/util/Platform';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useLibraryMutation, usePlausibleEvent } from '@sd/client';
|
||||
import { useLibraryMutation, usePlausibleEvent, useZodForm } from '@sd/client';
|
||||
import { Dialog, UseDialogProps, useDialog } from '@sd/ui';
|
||||
import { useZodForm } from '@sd/ui/src/forms';
|
||||
|
||||
interface Props extends UseDialogProps {
|
||||
onSuccess: () => void;
|
||||
|
||||
@@ -8,10 +8,11 @@ import {
|
||||
RuleKind,
|
||||
UnionToTuple,
|
||||
extractInfoRSPCError,
|
||||
useLibraryMutation
|
||||
useLibraryMutation,
|
||||
useZodForm
|
||||
} from '@sd/client';
|
||||
import { Button, Card, Divider, Input, Select, SelectOption, Tooltip } from '@sd/ui';
|
||||
import { ErrorMessage, Form, useZodForm, z } from '@sd/ui/src/forms';
|
||||
import { ErrorMessage, Form, z } from '@sd/ui/src/forms';
|
||||
import { InputKinds, RuleInput, validateInput } from './RuleInput';
|
||||
|
||||
const ruleKinds: UnionToTuple<RuleKind> = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Object, useLibraryMutation, usePlausibleEvent } from '@sd/client';
|
||||
import { Dialog, InputField, UseDialogProps, useDialog, useZodForm, z } from '@sd/ui';
|
||||
import { Object, useLibraryMutation, usePlausibleEvent, useZodForm } from '@sd/client';
|
||||
import { Dialog, InputField, UseDialogProps, useDialog, z } from '@sd/ui';
|
||||
import { ColorPicker } from '~/components';
|
||||
|
||||
const schema = z.object({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useLibraryMutation, usePlausibleEvent } from '@sd/client';
|
||||
import { useLibraryMutation, usePlausibleEvent, useZodForm } from '@sd/client';
|
||||
import { Dialog, UseDialogProps, useDialog } from '@sd/ui';
|
||||
import { useZodForm } from '@sd/ui/src/forms';
|
||||
|
||||
interface Props extends UseDialogProps {
|
||||
tagId: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Trash } from 'phosphor-react';
|
||||
import { Tag, useLibraryMutation } from '@sd/client';
|
||||
import { Button, Form, InputField, Switch, Tooltip, dialogManager, useZodForm, z } from '@sd/ui';
|
||||
import { Tag, useLibraryMutation, useZodForm } from '@sd/client';
|
||||
import { Button, Form, InputField, Switch, Tooltip, dialogManager, z } from '@sd/ui';
|
||||
import { ColorPicker } from '~/components';
|
||||
import { useDebouncedFormWatch } from '~/hooks';
|
||||
import Setting from '../../Setting';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { LibraryConfigWrapped, useBridgeMutation, usePlausibleEvent } from '@sd/client';
|
||||
import { Dialog, InputField, UseDialogProps, useDialog, useZodForm, z } from '@sd/ui';
|
||||
import { LibraryConfigWrapped, useBridgeMutation, usePlausibleEvent, useZodForm } from '@sd/client';
|
||||
import { Dialog, InputField, UseDialogProps, useDialog, z } from '@sd/ui';
|
||||
|
||||
const schema = z.object({
|
||||
name: z
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useBridgeMutation, usePlausibleEvent } from '@sd/client';
|
||||
import { Dialog, UseDialogProps, useDialog, useZodForm } from '@sd/ui';
|
||||
import { useBridgeMutation, usePlausibleEvent, useZodForm } from '@sd/client';
|
||||
import { Dialog, UseDialogProps, useDialog } from '@sd/ui';
|
||||
|
||||
interface Props extends UseDialogProps {
|
||||
libraryUuid: string;
|
||||
|
||||
@@ -8,10 +8,11 @@ import {
|
||||
telemetryStore,
|
||||
useBridgeMutation,
|
||||
useCachedLibraries,
|
||||
useMultiZodForm,
|
||||
useOnboardingStore,
|
||||
usePlausibleEvent
|
||||
} from '@sd/client';
|
||||
import { RadioGroupField, useZodForm, z } from '@sd/ui';
|
||||
import { RadioGroupField, z } from '@sd/ui';
|
||||
|
||||
export const OnboardingContext = createContext<ReturnType<typeof useContextValue> | null>(null);
|
||||
|
||||
@@ -46,21 +47,27 @@ export const shareTelemetry = RadioGroupField.options([
|
||||
}
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, 'Name is required').regex(/[\S]/g).trim(),
|
||||
shareTelemetry: shareTelemetry.schema
|
||||
});
|
||||
const schemas = {
|
||||
'new-library': z.object({
|
||||
name: z.string().min(1, 'Name is required').regex(/[\S]/g).trim()
|
||||
}),
|
||||
'privacy': z.object({
|
||||
shareTelemetry: shareTelemetry.schema
|
||||
})
|
||||
};
|
||||
|
||||
// this is a lot so it gets its own hook :)
|
||||
const useFormState = () => {
|
||||
const obStore = useOnboardingStore();
|
||||
|
||||
const form = useZodForm({
|
||||
schema,
|
||||
const { handleSubmit, ...forms } = useMultiZodForm({
|
||||
schemas,
|
||||
defaultValues: {
|
||||
name: obStore.newLibraryName,
|
||||
shareTelemetry: 'share-telemetry'
|
||||
}
|
||||
'new-library': obStore.data?.['new-library'] ?? undefined,
|
||||
'privacy': obStore.data?.privacy ?? {
|
||||
shareTelemetry: 'share-telemetry'
|
||||
}
|
||||
},
|
||||
onData: (data) => (getOnboardingStore().data = data)
|
||||
});
|
||||
|
||||
const navigate = useNavigate();
|
||||
@@ -69,42 +76,45 @@ const useFormState = () => {
|
||||
|
||||
const createLibrary = useBridgeMutation('library.create');
|
||||
|
||||
const onSubmit = form.handleSubmit(async (data) => {
|
||||
navigate('./creating-library', { replace: true });
|
||||
const submit = handleSubmit(
|
||||
async (data) => {
|
||||
navigate('./creating-library', { replace: true });
|
||||
|
||||
// opted to place this here as users could change their mind before library creation/onboarding finalization
|
||||
// it feels more fitting to configure it here (once)
|
||||
telemetryStore.shareFullTelemetry = getOnboardingStore().shareFullTelemetry;
|
||||
// opted to place this here as users could change their mind before library creation/onboarding finalization
|
||||
// it feels more fitting to configure it here (once)
|
||||
telemetryStore.shareFullTelemetry = data.privacy.shareTelemetry === 'share-telemetry';
|
||||
|
||||
try {
|
||||
// show creation screen for a bit for smoothness
|
||||
const [library] = await Promise.all([
|
||||
createLibrary.mutateAsync({
|
||||
name: data.name
|
||||
}),
|
||||
new Promise((res) => setTimeout(res, 500))
|
||||
]);
|
||||
try {
|
||||
// show creation screen for a bit for smoothness
|
||||
const [library] = await Promise.all([
|
||||
createLibrary.mutateAsync({
|
||||
name: data['new-library'].name
|
||||
}),
|
||||
new Promise((res) => setTimeout(res, 500))
|
||||
]);
|
||||
|
||||
queryClient.setQueryData(['library.list'], (libraries: any) => [
|
||||
...(libraries ?? []),
|
||||
library
|
||||
]);
|
||||
queryClient.setQueryData(['library.list'], (libraries: any) => [
|
||||
...(libraries ?? []),
|
||||
library
|
||||
]);
|
||||
|
||||
if (telemetryStore.shareFullTelemetry) {
|
||||
submitPlausibleEvent({ event: { type: 'libraryCreate' } });
|
||||
if (telemetryStore.shareFullTelemetry) {
|
||||
submitPlausibleEvent({ event: { type: 'libraryCreate' } });
|
||||
}
|
||||
|
||||
resetOnboardingStore();
|
||||
navigate(`/${library.uuid}/overview`, { replace: true });
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
alert(`Failed to create library. Error: ${e.message}`);
|
||||
}
|
||||
navigate('./privacy');
|
||||
}
|
||||
},
|
||||
(key) => navigate(`./${key}`)
|
||||
);
|
||||
|
||||
resetOnboardingStore();
|
||||
navigate(`/${library.uuid}/overview`, { replace: true });
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
alert(`Failed to create library. Error: ${e.message}`);
|
||||
}
|
||||
navigate('./privacy');
|
||||
}
|
||||
});
|
||||
|
||||
return { form, onSubmit };
|
||||
return { submit, forms };
|
||||
};
|
||||
|
||||
export const useOnboardingContext = () => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Database } from '@sd/assets/icons';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { getOnboardingStore } from '@sd/client';
|
||||
import { Button, Form, InputField } from '@sd/ui';
|
||||
import {
|
||||
OnboardingContainer,
|
||||
@@ -13,7 +12,8 @@ import { useOnboardingContext } from './context';
|
||||
|
||||
export default function OnboardingNewLibrary() {
|
||||
const navigate = useNavigate();
|
||||
const { form } = useOnboardingContext();
|
||||
const form = useOnboardingContext().forms.useForm('new-library');
|
||||
|
||||
const [importMode, setImportMode] = useState(false);
|
||||
|
||||
const handleImport = () => {
|
||||
@@ -23,11 +23,9 @@ export default function OnboardingNewLibrary() {
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
// manual onSubmit as we need to set the library name in the store
|
||||
onSubmit={async () => {
|
||||
getOnboardingStore().newLibraryName = form.getValues('name');
|
||||
onSubmit={form.handleSubmit(() => {
|
||||
navigate('../privacy', { replace: true });
|
||||
}}
|
||||
})}
|
||||
>
|
||||
<OnboardingContainer>
|
||||
<OnboardingImg src={Database} />
|
||||
@@ -53,17 +51,13 @@ export default function OnboardingNewLibrary() {
|
||||
{...form.register('name')}
|
||||
size="lg"
|
||||
autoFocus
|
||||
disabled={form.formState.isValid}
|
||||
className="mt-6 w-[300px]"
|
||||
placeholder={'e.g. "James\' Library"'}
|
||||
/>
|
||||
<div className="flex grow" />
|
||||
<div className="mt-7 space-x-2">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="accent"
|
||||
disabled={!form.formState.isValid}
|
||||
size="sm"
|
||||
>
|
||||
<Button type="submit" variant="accent" size="sm">
|
||||
New library
|
||||
</Button>
|
||||
{/* <span className="px-2 text-xs font-bold text-ink-faint">OR</span>
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { Button, Form, RadioGroupField } from '@sd/ui';
|
||||
import { getOnboardingStore } from '~/../packages/client/src';
|
||||
import { OnboardingContainer, OnboardingDescription, OnboardingTitle } from './Layout';
|
||||
import { shareTelemetry, useOnboardingContext } from './context';
|
||||
|
||||
export default function OnboardingPrivacy() {
|
||||
const { form, onSubmit } = useOnboardingContext();
|
||||
const { forms, submit } = useOnboardingContext();
|
||||
|
||||
const form = forms.useForm('privacy');
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
onSubmit={(e) => {
|
||||
getOnboardingStore().shareFullTelemetry =
|
||||
form.getValues('shareTelemetry') === 'share-telemetry';
|
||||
return onSubmit(e);
|
||||
}}
|
||||
onSubmit={form.handleSubmit(submit)}
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<OnboardingContainer>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
useDiscoveredPeers,
|
||||
useFeatureFlag,
|
||||
useP2PEvents,
|
||||
useZodForm,
|
||||
withFeatureFlag
|
||||
} from '@sd/client';
|
||||
import {
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
UseDialogProps,
|
||||
dialogManager,
|
||||
useDialog,
|
||||
useZodForm,
|
||||
z
|
||||
} from '@sd/ui';
|
||||
import { getSpacedropState, subscribeSpacedropState } from '../../hooks/useSpacedropState';
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
OperatingSystem,
|
||||
useBridgeMutation,
|
||||
useCachedLibraries,
|
||||
usePairingStatus
|
||||
usePairingStatus,
|
||||
useZodForm
|
||||
} from '@sd/client';
|
||||
import {
|
||||
Button,
|
||||
@@ -15,7 +16,6 @@ import {
|
||||
UseDialogProps,
|
||||
dialogManager,
|
||||
useDialog,
|
||||
useZodForm,
|
||||
z
|
||||
} from '@sd/ui';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Clipboard } from 'phosphor-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { useZodForm } from '@sd/client';
|
||||
import { Button, Dialog, Input, UseDialogProps, dialogManager, useDialog } from '@sd/ui';
|
||||
import { useZodForm } from '@sd/ui/src/forms';
|
||||
|
||||
interface Props extends UseDialogProps {
|
||||
title: string; // dialog title
|
||||
|
||||
Reference in new issue
Block a user