diff --git a/public/buckle-animated.gif b/public/buckle-animated.gif new file mode 100644 index 00000000..0fc7b0fc Binary files /dev/null and b/public/buckle-animated.gif differ diff --git a/public/buckle.png b/public/buckle.png new file mode 100644 index 00000000..00da33ad Binary files /dev/null and b/public/buckle.png differ diff --git a/src/context/dialog-context/dialog-context.tsx b/src/context/dialog-context/dialog-context.tsx index 95bc815b..52f3b91d 100644 --- a/src/context/dialog-context/dialog-context.tsx +++ b/src/context/dialog-context/dialog-context.tsx @@ -40,6 +40,10 @@ export interface DialogContext { openStarUsDialog: () => void; closeStarUsDialog: () => void; + // Buckle dialog + openBuckleDialog: () => void; + closeBuckleDialog: () => void; + // Export image dialog openExportImageDialog: ( params: Omit @@ -80,4 +84,6 @@ export const dialogContext = createContext({ closeExportDiagramDialog: emptyFn, openImportDiagramDialog: emptyFn, closeImportDiagramDialog: emptyFn, + openBuckleDialog: emptyFn, + closeBuckleDialog: emptyFn, }); diff --git a/src/context/dialog-context/dialog-provider.tsx b/src/context/dialog-context/dialog-provider.tsx index 485e4ceb..1a1f1bbb 100644 --- a/src/context/dialog-context/dialog-provider.tsx +++ b/src/context/dialog-context/dialog-provider.tsx @@ -17,6 +17,7 @@ import type { ExportImageDialogProps } from '@/dialogs/export-image-dialog/expor import { ExportImageDialog } from '@/dialogs/export-image-dialog/export-image-dialog'; import { ExportDiagramDialog } from '@/dialogs/export-diagram-dialog/export-diagram-dialog'; import { ImportDiagramDialog } from '@/dialogs/import-diagram-dialog/import-diagram-dialog'; +import { BuckleDialog } from '@/dialogs/buckle-dialog/buckle-dialog'; export const DialogProvider: React.FC = ({ children, @@ -27,6 +28,7 @@ export const DialogProvider: React.FC = ({ const [openCreateRelationshipDialog, setOpenCreateRelationshipDialog] = useState(false); const [openStarUsDialog, setOpenStarUsDialog] = useState(false); + const [openBuckleDialog, setOpenBuckleDialog] = useState(false); // Export image dialog const [openExportImageDialog, setOpenExportImageDialog] = useState(false); @@ -114,6 +116,8 @@ export const DialogProvider: React.FC = ({ closeTableSchemaDialog: () => setOpenTableSchemaDialog(false), openStarUsDialog: () => setOpenStarUsDialog(true), closeStarUsDialog: () => setOpenStarUsDialog(false), + closeBuckleDialog: () => setOpenBuckleDialog(false), + openBuckleDialog: () => setOpenBuckleDialog(true), closeExportImageDialog: () => setOpenExportImageDialog(false), openExportImageDialog: openExportImageDialogHandler, openExportDiagramDialog: () => setOpenExportDiagramDialog(true), @@ -149,6 +153,7 @@ export const DialogProvider: React.FC = ({ /> + ); }; diff --git a/src/context/local-config-context/local-config-context.tsx b/src/context/local-config-context/local-config-context.tsx index b8982ce7..2445c16c 100644 --- a/src/context/local-config-context/local-config-context.tsx +++ b/src/context/local-config-context/local-config-context.tsx @@ -30,6 +30,12 @@ export interface LocalConfigContext { starUsDialogLastOpen: number; setStarUsDialogLastOpen: (lastOpen: number) => void; + buckleWaitlistOpened: boolean; + setBuckleWaitlistOpened: (githubRepoOpened: boolean) => void; + + buckleDialogLastOpen: number; + setBuckleDialogLastOpen: (lastOpen: number) => void; + showDependenciesOnCanvas: boolean; setShowDependenciesOnCanvas: (showDependenciesOnCanvas: boolean) => void; } @@ -56,6 +62,12 @@ export const LocalConfigContext = createContext({ starUsDialogLastOpen: 0, setStarUsDialogLastOpen: emptyFn, + buckleWaitlistOpened: false, + setBuckleWaitlistOpened: emptyFn, + + buckleDialogLastOpen: 0, + setBuckleDialogLastOpen: emptyFn, + showDependenciesOnCanvas: false, setShowDependenciesOnCanvas: emptyFn, }); diff --git a/src/context/local-config-context/local-config-provider.tsx b/src/context/local-config-context/local-config-provider.tsx index ceb94309..852695be 100644 --- a/src/context/local-config-context/local-config-provider.tsx +++ b/src/context/local-config-context/local-config-provider.tsx @@ -10,6 +10,8 @@ const showCardinalityKey = 'show_cardinality'; const hideMultiSchemaNotificationKey = 'hide_multi_schema_notification'; const githubRepoOpenedKey = 'github_repo_opened'; const starUsDialogLastOpenKey = 'star_us_dialog_last_open'; +const buckleWaitlistOpenedKey = 'buckle_waitlist_opened'; +const buckleDialogLastOpenKey = 'buckle_dialog_last_open'; const showDependenciesOnCanvasKey = 'show_dependencies_on_canvas'; export const LocalConfigProvider: React.FC = ({ @@ -48,6 +50,17 @@ export const LocalConfigProvider: React.FC = ({ parseInt(localStorage.getItem(starUsDialogLastOpenKey) || '0') ); + const [buckleWaitlistOpened, setBuckleWaitlistOpened] = + React.useState( + (localStorage.getItem(buckleWaitlistOpenedKey) || 'false') === + 'true' + ); + + const [buckleDialogLastOpen, setBuckleDialogLastOpen] = + React.useState( + parseInt(localStorage.getItem(buckleDialogLastOpenKey) || '0') + ); + const [showDependenciesOnCanvas, setShowDependenciesOnCanvas] = React.useState( (localStorage.getItem(showDependenciesOnCanvasKey) || 'false') === @@ -65,6 +78,20 @@ export const LocalConfigProvider: React.FC = ({ localStorage.setItem(githubRepoOpenedKey, githubRepoOpened.toString()); }, [githubRepoOpened]); + useEffect(() => { + localStorage.setItem( + buckleDialogLastOpenKey, + buckleDialogLastOpen.toString() + ); + }, [buckleDialogLastOpen]); + + useEffect(() => { + localStorage.setItem( + buckleWaitlistOpenedKey, + buckleWaitlistOpened.toString() + ); + }, [buckleWaitlistOpened]); + useEffect(() => { localStorage.setItem( hideMultiSchemaNotificationKey, @@ -114,6 +141,10 @@ export const LocalConfigProvider: React.FC = ({ setStarUsDialogLastOpen, showDependenciesOnCanvas, setShowDependenciesOnCanvas, + setBuckleDialogLastOpen, + buckleDialogLastOpen, + buckleWaitlistOpened, + setBuckleWaitlistOpened, }} > {children} diff --git a/src/dialogs/buckle-dialog/buckle-dialog.tsx b/src/dialogs/buckle-dialog/buckle-dialog.tsx new file mode 100644 index 00000000..929ca3fb --- /dev/null +++ b/src/dialogs/buckle-dialog/buckle-dialog.tsx @@ -0,0 +1,80 @@ +import React, { useCallback, useEffect } from 'react'; +import { useDialog } from '@/hooks/use-dialog'; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/dialog/dialog'; +import { Button } from '@/components/button/button'; +import type { BaseDialogProps } from '../common/base-dialog-props'; +import { useLocalConfig } from '@/hooks/use-local-config'; +import { useTheme } from '@/hooks/use-theme'; + +export interface BuckleDialogProps extends BaseDialogProps {} + +export const BuckleDialog: React.FC = ({ dialog }) => { + const { setBuckleWaitlistOpened } = useLocalConfig(); + const { effectiveTheme } = useTheme(); + + useEffect(() => { + if (!dialog.open) return; + }, [dialog.open]); + const { closeBuckleDialog } = useDialog(); + + const handleConfirm = useCallback(() => { + setBuckleWaitlistOpened(true); + window.open('https://waitlist.buckle.dev', '_blank'); + }, [setBuckleWaitlistOpened]); + + return ( + { + if (!open) { + closeBuckleDialog(); + } + }} + > + { + e.preventDefault(); + }} + > + + + + +
+ +
+ We've been working on something big -{' '} + Ready to explore? +
+
+ + + + + + + + +
+
+ ); +}; diff --git a/src/pages/editor-page/editor-page.tsx b/src/pages/editor-page/editor-page.tsx index 4c93f70d..eb65e6ca 100644 --- a/src/pages/editor-page/editor-page.tsx +++ b/src/pages/editor-page/editor-page.tsx @@ -41,6 +41,10 @@ import { AlertProvider } from '@/context/alert-context/alert-provider'; const OPEN_STAR_US_AFTER_SECONDS = 30; const SHOW_STAR_US_AGAIN_AFTER_DAYS = 1; +const OPEN_BUCKLE_AFTER_SECONDS = 60; +const SHOW_BUCKLE_AGAIN_AFTER_DAYS = 1; +const SHOW_BUCKLE_AGAIN_OPENED_AFTER_DAYS = 7; + export const EditorDesktopLayoutLazy = React.lazy( () => import('./editor-desktop-layout') ); @@ -60,7 +64,8 @@ const EditorPageComponent: React.FC = () => { const { openSelectSchema, showSidePanel } = useLayout(); const { resetRedoStack, resetUndoStack } = useRedoUndoStack(); const { showLoader, hideLoader } = useFullScreenLoader(); - const { openCreateDiagramDialog, openStarUsDialog } = useDialog(); + const { openCreateDiagramDialog, openStarUsDialog, openBuckleDialog } = + useDialog(); const { diagramId } = useParams<{ diagramId: string }>(); const { config, updateConfig } = useConfig(); const navigate = useNavigate(); @@ -72,6 +77,9 @@ const EditorPageComponent: React.FC = () => { starUsDialogLastOpen, setStarUsDialogLastOpen, githubRepoOpened, + setBuckleDialogLastOpen, + buckleDialogLastOpen, + buckleWaitlistOpened, } = useLocalConfig(); const { toast } = useToast(); const { t } = useTranslation(); @@ -164,6 +172,33 @@ const EditorPageComponent: React.FC = () => { starUsDialogLastOpen, ]); + useEffect(() => { + if (!currentDiagram?.id) { + return; + } + + if ( + new Date().getTime() - buckleDialogLastOpen > + 1000 * + 60 * + 60 * + 24 * + (buckleWaitlistOpened + ? SHOW_BUCKLE_AGAIN_OPENED_AFTER_DAYS + : SHOW_BUCKLE_AGAIN_AFTER_DAYS) + ) { + const lastOpen = new Date().getTime(); + setBuckleDialogLastOpen(lastOpen); + setTimeout(openBuckleDialog, OPEN_BUCKLE_AFTER_SECONDS * 1000); + } + }, [ + currentDiagram?.id, + buckleWaitlistOpened, + openBuckleDialog, + setBuckleDialogLastOpen, + buckleDialogLastOpen, + ]); + const lastDiagramId = useRef(''); const handleChangeSchema = useCallback(async () => {