Skip to content

Commit

Permalink
feat: toast refactor (#347)
Browse files Browse the repository at this point in the history
* feat: snackbar refactor with solid-toast
* docs(changeset): refactor snackbar with solid-toast
* feat(app): handle toast on success/error
  • Loading branch information
riccardoperra committed Sep 4, 2022
1 parent 29ae982 commit f726e57
Show file tree
Hide file tree
Showing 27 changed files with 287 additions and 185 deletions.
5 changes: 5 additions & 0 deletions .changeset/strange-otters-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@codeimage/ui': minor
---

refactor snackbar with solid-toast
28 changes: 19 additions & 9 deletions apps/codeimage/src/components/Toolbar/ExportButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
SegmentedField,
SegmentedFieldItem,
TextField,
useSnackbarStore,
toast,
VStack,
} from '@codeimage/ui';
import {useModality} from '@core/hooks/isMobile';
Expand All @@ -25,7 +25,14 @@ import {
createOverlayTriggerState,
OverlayContainer,
} from '@solid-aria/overlays';
import {Component, createEffect, createSignal, onMount, Show} from 'solid-js';
import {
Component,
createEffect,
createSignal,
onMount,
Show,
untrack,
} from 'solid-js';
import {
ExportExtension,
ExportMode,
Expand All @@ -43,7 +50,6 @@ interface ExportButtonProps {

export const ExportButton: Component<ExportButtonProps> = props => {
let openButtonRef: HTMLButtonElement | undefined;
const snackbarStore = useSnackbarStore();
const [t] = useI18n<AppLocaleEntries>();
const modality = useModality();
const overlayState = createOverlayTriggerState({});
Expand All @@ -62,12 +68,16 @@ export const ExportButton: Component<ExportButtonProps> = props => {

createEffect(() => {
if (data.error) {
snackbarStore.create({
closeable: true,
message: () => {
const [t] = useI18n<AppLocaleEntries>();
return <>{t('export.genericSaveError')}</>;
},
untrack(() => {
toast.error(
() => {
const [t] = useI18n<AppLocaleEntries>();
return <>{t('export.genericSaveError')}</>;
},
{
position: 'bottom-center',
},
);
});
}
});
Expand Down
21 changes: 12 additions & 9 deletions apps/codeimage/src/components/Toolbar/ExportNewTabButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useI18n} from '@codeimage/locale';
import {Button, useSnackbarStore} from '@codeimage/ui';
import {Component, createEffect} from 'solid-js';
import {Button, toast} from '@codeimage/ui';
import {Component, createEffect, untrack} from 'solid-js';
import {
ExportExtension,
ExportMode,
Expand All @@ -15,7 +15,6 @@ interface ExportButtonProps {
}

export const ExportInNewTabButton: Component<ExportButtonProps> = props => {
const snackbarStore = useSnackbarStore();
const [t] = useI18n<AppLocaleEntries>();

const [data, notify] = useExportImage();
Expand All @@ -39,12 +38,16 @@ export const ExportInNewTabButton: Component<ExportButtonProps> = props => {

createEffect(() => {
if (data.error) {
snackbarStore.create({
closeable: true,
message: () => {
const [t] = useI18n<AppLocaleEntries>();
return <>{t('export.genericSaveError')}</>;
},
untrack(() => {
toast.error(
() => {
const [t] = useI18n<AppLocaleEntries>();
return <>{t('export.genericSaveError')}</>;
},
{
position: 'bottom-center',
},
);
});
}
});
Expand Down
20 changes: 9 additions & 11 deletions apps/codeimage/src/core/hooks/async-action.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import {
Accessor,
createResource,
createSignal,
from,
mergeProps,
Resource,
} from 'solid-js';
import {Observable, Subject} from 'rxjs';
import {ResourceActions} from 'solid-js/types/reactive/signal';

interface AsyncResourceActions<T, R> extends ResourceActions<R | undefined> {
notify: (value: T) => void;
notify: (value?: T) => void;
}

export function useAsyncAction<T, R>(
export function createAsyncAction<T, R>(
fetcher: (ref: T) => Promise<R>,
notifier?: Subject<T>,
notifier?: Accessor<T>,
): [Resource<R | undefined>, AsyncResourceActions<T, R | undefined>] {
const [internalNotifier, setNotifier] = createSignal<T | undefined>(
const [internalNotifier, setNotifier] = createSignal<T | symbol | undefined>(
undefined,
{
equals: false,
},
);

const [resource, _resourceActions] = createResource(
(notifier instanceof Observable ? from(notifier) : notifier) ||
internalNotifier,
fetcher,
notifier || internalNotifier,
async args => fetcher(args as T),
);

const notify = (value: T) => {
return setNotifier(() => value);
const notify = (value?: T) => {
return setNotifier(() => value ?? Symbol());
};

const resourceActions = mergeProps(
Expand Down
14 changes: 8 additions & 6 deletions apps/codeimage/src/hooks/use-export-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
toSvg,
} from '@codeimage/dom-export';
import {EXPORT_EXCLUDE} from '@core/directives/exportExclude';
import {useAsyncAction} from '@core/hooks/async-action';
import {createAsyncAction} from '@core/hooks/async-action';
import {useWebshare} from '@core/hooks/use-webshare';
import {isIOS} from '@solid-primitives/platform';
import download from 'downloadjs';
Expand Down Expand Up @@ -43,11 +43,13 @@ export function useExportImage(): [
Resource<Blob | string | undefined>,
(data: ExportImagePayload) => void,
] {
const [data, {notify}] = useAsyncAction(async (ref: ExportImagePayload) => {
// @bad Find another way to prevent flickering
await new Promise(r => setTimeout(r, 50));
return exportImage(ref);
});
const [data, {notify}] = createAsyncAction(
async (ref: ExportImagePayload) => {
// @bad Find another way to prevent flickering
await new Promise(r => setTimeout(r, 50));
return exportImage(ref);
},
);

return [data, notify];
}
Expand Down
18 changes: 18 additions & 0 deletions apps/codeimage/src/i18n/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ export const dashboard = {
dashboard: {
myProjects: 'I miei progetti',
new: 'Nuovo',
errorCreatingProject: 'Si è verificato un errore!',
projectCreateSuccess: 'Progetto creato con successo',
projectCloneSuccess: 'Progetto {{name}} clonato con successo',
projectDeleteSuccess: 'Il progetto {{name}} è stato eliminato',
projectDeleteError: 'Impossibile eliminare il progetto {{name}}',
deleteProject: {
dropdownLabel: 'Rimuovi',
confirmTitle: 'Rimuovi progetto',
Expand Down Expand Up @@ -39,6 +44,11 @@ export const dashboard = {
dashboard: {
myProjects: 'My projects',
new: 'New',
errorCreatingProject: 'An error occurred!',
projectCreateSuccess: 'Project created successfully',
projectCloneSuccess: 'Project {{name}} cloned successfully',
projectDeleteSuccess: 'Project {{name}} has been deleted',
projectDeleteError: 'Cannot delete project {{name}}',
empty: {
title: 'No projects yet',
description:
Expand Down Expand Up @@ -74,6 +84,10 @@ export const dashboard = {
dashboard: {
myProjects: 'My projects',
new: 'New',
errorCreatingProject: 'An error occurred!',
projectCloneSuccess: 'Project {{name}} cloned successfully',
projectDeleteSuccess: 'Project {{name}} has been deleted',
projectDeleteError: 'Cannot delete project {{name}}',
created: 'Created',
updated: 'Updated',
empty: {
Expand Down Expand Up @@ -111,6 +125,10 @@ export const dashboard = {
new: 'New',
created: 'Created',
updated: 'Updated',
errorCreatingProject: 'An error occurred!',
projectCloneSuccess: 'Project {{name}} cloned successfully',
projectDeleteSuccess: 'Project {{name}} has been deleted',
projectDeleteError: 'Cannot delete project {{name}}',
empty: {
title: 'No projects yet',
description:
Expand Down
25 changes: 15 additions & 10 deletions apps/codeimage/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import {createI18nContext, I18nContext, useI18n} from '@codeimage/locale';
import {getAuth0State} from '@codeimage/store/auth/auth0';
import {getRootEditorStore} from '@codeimage/store/editor';
import {uiStore} from '@codeimage/store/ui';
import {backgroundColorVar, CodeImageThemeProvider} from '@codeimage/ui';
import {
backgroundColorVar,
CodeImageThemeProvider,
SnackbarHost,
} from '@codeimage/ui';
import {ThemeProviderProps} from '@codeimage/ui/src';
import {enableUmami} from '@core/constants/umami';
import {OverlayProvider} from '@solid-aria/overlays';
import {setElementVars} from '@vanilla-extract/dynamic';
import {Router, useRoutes} from '@solidjs/router';
import {setElementVars} from '@vanilla-extract/dynamic';
import {
Component,
createEffect,
Expand Down Expand Up @@ -41,7 +46,7 @@ function lazyWithNoLauncher(cp: () => Promise<{default: Component<any>}>) {
});
}

const theme: Parameters<typeof CodeImageThemeProvider>[0]['theme'] = {
const tokens: ThemeProviderProps['tokens'] = {
text: {
weight: 'medium',
},
Expand Down Expand Up @@ -116,7 +121,6 @@ export function Bootstrap() {
if (scheme) {
const color = theme === 'dark' ? darkGrayScale.gray1 : '#FFFFFF';
scheme.setAttribute('content', color);
document.body.setAttribute('data-codeimage-theme', theme);
setElementVars(document.body, {
[backgroundColorVar]: color,
});
Expand All @@ -126,15 +130,16 @@ export function Bootstrap() {

return (
<Scaffold>
<OverlayProvider>
<Router>
<CodeImageThemeProvider theme={theme}>
<CodeImageThemeProvider tokens={tokens} theme={uiStore.themeMode}>
<SnackbarHost />
<OverlayProvider>
<Router>
<Suspense>
<Routes />
</Suspense>
</CodeImageThemeProvider>
</Router>
</OverlayProvider>
</Router>
</OverlayProvider>
</CodeImageThemeProvider>
<SidebarPopoverHost />
</Scaffold>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useI18n} from '@codeimage/locale';
import {Box, Button, Loading} from '@codeimage/ui';
import {useAsyncAction} from '@core/hooks/async-action';
import {Box, Button, Loading, toast} from '@codeimage/ui';
import {createAsyncAction} from '@core/hooks/async-action';
import {useNavigate} from '@solidjs/router';
import {Show} from 'solid-js';
import {PlusIcon} from '../../../../components/Icons/PlusIcon';
Expand All @@ -11,16 +11,31 @@ export function CreateNewProjectButton() {
const navigate = useNavigate();
const dashboard = getDashboardState()!;
const [t] = useI18n<AppLocaleEntries>();
const [data, {notify}] = useAsyncAction(createNew);
const [data, {notify}] = createAsyncAction(createNew);

async function createNew() {
const result = await dashboard?.createNewProject();
if (!result) return;
navigate(`/${result.id}`);
try {
const result = await dashboard?.createNewProject();
if (!result) return;
toast.success(t('dashboard.projectCreateSuccess'), {
position: 'bottom-center',
duration: 100000,
});
navigate(`/${result.id}`);
} catch (e) {
toast.error(t('dashboard.errorCreatingProject'));
}
}

return (
<Button theme="primary" variant="solid" onClick={() => notify(0)}>
<Button
theme="primary"
variant="solid"
onClick={() => {
notify();
console.log('notify');
}}
>
<Show when={data.loading} fallback={<PlusIcon size={'sm'} />}>
<Loading size={'sm'} />
</Show>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
IconButton,
MenuButton,
Text,
toast,
} from '@codeimage/ui';
import {highlight as _highlight} from '@core/directives/highlight';
import {formatDistanceToNow} from '@core/helpers/date';
Expand Down Expand Up @@ -59,6 +60,38 @@ export function ProjectItem(props: VoidProps<ProjectItemProps>) {
);
};

async function clone() {
try {
const result = await dashboard.cloneProject(props.item);
if (!result) throw new Error('Error');
toast.success(
t('dashboard.projectCloneSuccess', {name: props.item.name}),
{
position: 'bottom-center',
},
);
navigate(`/${result.id}`);
} catch (e) {
toast.error(t('dashboard.errorCreatingProject'));
}
}

async function deleteConfirm() {
const oldData = dashboard.data.latest ?? [];
try {
await dashboard?.deleteProject(props.item.id);
toast.success(
t('dashboard.projectCloneSuccess', {name: props.item.name}),
{
position: 'bottom-center',
},
);
} catch (e) {
toast.error(t('dashboard.errorCreatingProject'));
dashboard.mutateData(oldData);
}
}

return (
<li class={styles.item}>
<Link class={styles.itemLink} href={`/${props.item.id}`} />
Expand All @@ -85,7 +118,7 @@ export function ProjectItem(props: VoidProps<ProjectItemProps>) {
title: t('dashboard.deleteProject.confirmTitle'),
message: t('dashboard.deleteProject.confirmMessage'),
onConfirm: () => {
dashboard?.deleteProject(props.item.id);
deleteConfirm();
state.close();
},
actionType: 'danger' as const,
Expand All @@ -107,9 +140,7 @@ export function ProjectItem(props: VoidProps<ProjectItemProps>) {
}));
}
if (action === 'clone') {
dashboard
.cloneProject(props.item)
?.then(result => navigate(`/${result.id}`));
clone();
}
}}
>
Expand Down
Loading

0 comments on commit f726e57

Please sign in to comment.