diff --git a/package-lock.json b/package-lock.json index 86ebae2..0cd0f99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,12 @@ "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slider": "^1.2.1", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-toast": "^1.2.1", "@tiptap/extension-list-keymap": "^2.6.6", "@tiptap/extension-mention": "^2.8.0", @@ -4094,6 +4096,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", @@ -4499,6 +4524,50 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz", + "integrity": "sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.1.tgz", diff --git a/package.json b/package.json index 38a84fc..02c71af 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,12 @@ "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slider": "^1.2.1", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-toast": "^1.2.1", "@tiptap/extension-list-keymap": "^2.6.6", "@tiptap/extension-mention": "^2.8.0", diff --git a/src/common/components/shadcn/label.tsx b/src/common/components/shadcn/label.tsx new file mode 100644 index 0000000..452e7f3 --- /dev/null +++ b/src/common/components/shadcn/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/common/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/src/common/components/shadcn/switch.tsx b/src/common/components/shadcn/switch.tsx new file mode 100644 index 0000000..f573f1d --- /dev/null +++ b/src/common/components/shadcn/switch.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/common/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/src/container/Settings/Settings.tsx b/src/container/Settings/Settings.tsx index cc40e14..19dbf45 100644 --- a/src/container/Settings/Settings.tsx +++ b/src/container/Settings/Settings.tsx @@ -32,9 +32,17 @@ import { tagStyles, type TagStyle, } from "../../scripts/theme/tagStyles"; +import { + disableDemoMode, + setDemoModeUntil, + useDemoModeStore, +} from "@/scripts/store/demoModeStore"; +import { Switch } from "@/common/components/shadcn/switch"; +import { Label } from "@/common/components/shadcn/label"; export const Settings = () => { const { toast } = useToast(); + const isDemoMode = useDemoModeStore((s) => !!s.context.demoModeUntil); const [isAutomaticBackupsEnabled, setIsAutomaticBackupsEnabled] = useState( fileSystemService.isAutomaticBackupEnabled(), ); @@ -202,7 +210,7 @@ export const Settings = () => {
-

Theme

+

Appearance

@@ -235,6 +243,24 @@ export const Settings = () => {
+
+ + + + { + if (isDemoMode) { + disableDemoMode(); + } else { + setDemoModeUntil(new Date()); + } + }} + /> +
diff --git a/src/container/SparkList/SparkItem/SparkItem.tsx b/src/container/SparkList/SparkItem/SparkItem.tsx index 104d27d..00e5d8a 100644 --- a/src/container/SparkList/SparkItem/SparkItem.tsx +++ b/src/container/SparkList/SparkItem/SparkItem.tsx @@ -18,12 +18,13 @@ import { Button } from "../../../common/components/shadcn/button"; type Props = { spark: Spark; + blur?: boolean; }; export const SparkItem = (props: Props) => { const [isEditing, setIsEditing] = useState(false); const [isDeleting, setIsDeleting] = useState(false); - const { spark } = props; + const { spark, blur = "false" } = props; const handleSubmit = async (plainText: string, html: string) => { await sparkService.updateSpark(spark.id, plainText, html); @@ -45,7 +46,7 @@ export const SparkItem = (props: Props) => { return (
{isEditing ? (
diff --git a/src/container/SparkList/SparkList.tsx b/src/container/SparkList/SparkList.tsx index 29c27cf..d393f8d 100644 --- a/src/container/SparkList/SparkList.tsx +++ b/src/container/SparkList/SparkList.tsx @@ -1,12 +1,13 @@ import { useLiveQuery } from "dexie-react-hooks"; import { sparkService } from "../../scripts/db/SparkService"; -import { differenceInCalendarDays, format } from "date-fns"; +import { differenceInCalendarDays, format, isAfter } from "date-fns"; import { EmptyState } from "../../common/components/EmptyState/EmptyState"; import type { Spark } from "../../interfaces/Spark"; import { useQueryStore } from "../../scripts/store/queryStore"; import { SparkItem } from "./SparkItem/SparkItem"; import { Tag as TagElement } from "../../common/components/Tag/Tag"; import type { Tag } from "../../interfaces/Tag"; +import { useDemoModeStore } from "../../scripts/store/demoModeStore"; type SparkSection = { key: string; @@ -29,6 +30,7 @@ export const SparkList = () => { () => sparkService.find(queryTags), [queryTags], ); + const demoModeUntil = useDemoModeStore((s) => s.context.demoModeUntil); const sections = sparksWithTags?.reduce( (tmpSections, { spark, contextTagData }) => { @@ -140,6 +142,14 @@ export const SparkList = () => { ))}
diff --git a/src/scripts/store/demoModeStore.ts b/src/scripts/store/demoModeStore.ts new file mode 100644 index 0000000..e4883c4 --- /dev/null +++ b/src/scripts/store/demoModeStore.ts @@ -0,0 +1,57 @@ +import { createStore, type SnapshotFromStore } from "@xstate/store"; +import { debounce } from "../utils/debounce"; +import { extractTags } from "../utils/stringUtils"; +import { useSelector } from "@xstate/store/react"; + +export const demoModeStore = createStore({ + // Initial context + context: { demoModeUntil: undefined } as { + demoModeUntil: Date | undefined + }, + // Transitions + on: { + update: { + demoModeUntil: (_context, event: { until: Date | undefined }) => { + return event.until; + }, + }, + }, +}); + +export const setDemoModeUntil = (until: Date) => { + demoModeStore.send({ + type: 'update', + until, + }) +} + +export const disableDemoMode = () => { + demoModeStore.send({ + type: 'update', + until: undefined, + }) +} + +export const useDemoModeStore = ( + selector: (snapshot: SnapshotFromStore) => T, +) => { + return useSelector(demoModeStore, selector); +}; + +declare global { + interface Window { + demoModeStore: typeof demoModeStore; + debugDemoModeStore?: boolean; + } +} + +if (typeof window !== "undefined") { + window.demoModeStore = demoModeStore; +} + +// window.debugQueryStore = true; +demoModeStore.inspect((event) => { + if (window.debugDemoModeStore) { + console.log(event); + } +}); \ No newline at end of file