Skip to content

Commit f604fe2

Browse files
authored
Add demo mode
1 parent c9ff672 commit f604fe2

File tree

8 files changed

+220
-4
lines changed

8 files changed

+220
-4
lines changed

package-lock.json

Lines changed: 69 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
"@radix-ui/react-alert-dialog": "^1.1.2",
2222
"@radix-ui/react-dialog": "^1.1.1",
2323
"@radix-ui/react-icons": "^1.3.0",
24+
"@radix-ui/react-label": "^2.1.0",
2425
"@radix-ui/react-popover": "^1.1.2",
2526
"@radix-ui/react-select": "^2.1.2",
2627
"@radix-ui/react-slider": "^1.2.1",
2728
"@radix-ui/react-slot": "^1.1.0",
29+
"@radix-ui/react-switch": "^1.1.1",
2830
"@radix-ui/react-toast": "^1.2.1",
2931
"@tiptap/extension-list-keymap": "^2.6.6",
3032
"@tiptap/extension-mention": "^2.8.0",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as React from "react"
2+
import * as LabelPrimitive from "@radix-ui/react-label"
3+
import { cva, type VariantProps } from "class-variance-authority"
4+
5+
import { cn } from "@/common/lib/utils"
6+
7+
const labelVariants = cva(
8+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9+
)
10+
11+
const Label = React.forwardRef<
12+
React.ElementRef<typeof LabelPrimitive.Root>,
13+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
14+
VariantProps<typeof labelVariants>
15+
>(({ className, ...props }, ref) => (
16+
<LabelPrimitive.Root
17+
ref={ref}
18+
className={cn(labelVariants(), className)}
19+
{...props}
20+
/>
21+
))
22+
Label.displayName = LabelPrimitive.Root.displayName
23+
24+
export { Label }
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as React from "react"
2+
import * as SwitchPrimitives from "@radix-ui/react-switch"
3+
4+
import { cn } from "@/common/lib/utils"
5+
6+
const Switch = React.forwardRef<
7+
React.ElementRef<typeof SwitchPrimitives.Root>,
8+
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
9+
>(({ className, ...props }, ref) => (
10+
<SwitchPrimitives.Root
11+
className={cn(
12+
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
13+
className
14+
)}
15+
{...props}
16+
ref={ref}
17+
>
18+
<SwitchPrimitives.Thumb
19+
className={cn(
20+
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
21+
)}
22+
/>
23+
</SwitchPrimitives.Root>
24+
))
25+
Switch.displayName = SwitchPrimitives.Root.displayName
26+
27+
export { Switch }

src/container/Settings/Settings.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,17 @@ import {
3232
tagStyles,
3333
type TagStyle,
3434
} from "../../scripts/theme/tagStyles";
35+
import {
36+
disableDemoMode,
37+
setDemoModeUntil,
38+
useDemoModeStore,
39+
} from "@/scripts/store/demoModeStore";
40+
import { Switch } from "@/common/components/shadcn/switch";
41+
import { Label } from "@/common/components/shadcn/label";
3542

3643
export const Settings = () => {
3744
const { toast } = useToast();
45+
const isDemoMode = useDemoModeStore((s) => !!s.context.demoModeUntil);
3846
const [isAutomaticBackupsEnabled, setIsAutomaticBackupsEnabled] = useState(
3947
fileSystemService.isAutomaticBackupEnabled(),
4048
);
@@ -202,7 +210,7 @@ export const Settings = () => {
202210
</div>
203211
<div className="pb-10 px-16 grid grid-cols-3 gap-10">
204212
<div className="flex flex-col gap-4">
205-
<h3 className="font-semibold">Theme</h3>
213+
<h3 className="font-semibold">Appearance</h3>
206214
<div className="flex flex-col gap-4 p-1">
207215
<div className="flex flex-col gap-2">
208216
<span className="flex flex-row gap-1">
@@ -235,6 +243,24 @@ export const Settings = () => {
235243
</SelectContent>
236244
</Select>
237245
</div>
246+
<div className="flex flex-col gap-2">
247+
<span className="flex flex-row gap-1">
248+
<Label htmlFor="demo-mode-switch">
249+
Demo Mode
250+
</Label>
251+
</span>
252+
<Switch
253+
id="demo-mode-switch"
254+
checked={isDemoMode}
255+
onCheckedChange={() => {
256+
if (isDemoMode) {
257+
disableDemoMode();
258+
} else {
259+
setDemoModeUntil(new Date());
260+
}
261+
}}
262+
/>
263+
</div>
238264
</div>
239265
</div>
240266
<div className="flex flex-col gap-4">

src/container/SparkList/SparkItem/SparkItem.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ import { Button } from "../../../common/components/shadcn/button";
1818

1919
type Props = {
2020
spark: Spark;
21+
blur?: boolean;
2122
};
2223

2324
export const SparkItem = (props: Props) => {
2425
const [isEditing, setIsEditing] = useState(false);
2526
const [isDeleting, setIsDeleting] = useState(false);
26-
const { spark } = props;
27+
const { spark, blur = "false" } = props;
2728

2829
const handleSubmit = async (plainText: string, html: string) => {
2930
await sparkService.updateSpark(spark.id, plainText, html);
@@ -45,7 +46,7 @@ export const SparkItem = (props: Props) => {
4546
return (
4647
<article
4748
key={spark.id}
48-
className="flex group relative"
49+
className={`flex group relative ${blur ? "blur-sm" : ""}`}
4950
>
5051
{isEditing ? (
5152
<div className="w-full h-full">

src/container/SparkList/SparkList.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { useLiveQuery } from "dexie-react-hooks";
22
import { sparkService } from "../../scripts/db/SparkService";
3-
import { differenceInCalendarDays, format } from "date-fns";
3+
import { differenceInCalendarDays, format, isAfter } from "date-fns";
44
import { EmptyState } from "../../common/components/EmptyState/EmptyState";
55
import type { Spark } from "../../interfaces/Spark";
66
import { useQueryStore } from "../../scripts/store/queryStore";
77
import { SparkItem } from "./SparkItem/SparkItem";
88
import { Tag as TagElement } from "../../common/components/Tag/Tag";
99
import type { Tag } from "../../interfaces/Tag";
10+
import { useDemoModeStore } from "../../scripts/store/demoModeStore";
1011

1112
type SparkSection = {
1213
key: string;
@@ -29,6 +30,7 @@ export const SparkList = () => {
2930
() => sparkService.find(queryTags),
3031
[queryTags],
3132
);
33+
const demoModeUntil = useDemoModeStore((s) => s.context.demoModeUntil);
3234

3335
const sections = sparksWithTags?.reduce<Section[]>(
3436
(tmpSections, { spark, contextTagData }) => {
@@ -140,6 +142,14 @@ export const SparkList = () => {
140142
<SparkItem
141143
key={spark.id}
142144
spark={spark}
145+
blur={
146+
demoModeUntil
147+
? isAfter(
148+
demoModeUntil,
149+
spark.creationDate,
150+
)
151+
: false
152+
}
143153
/>
144154
))}
145155
</div>

src/scripts/store/demoModeStore.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { createStore, type SnapshotFromStore } from "@xstate/store";
2+
import { debounce } from "../utils/debounce";
3+
import { extractTags } from "../utils/stringUtils";
4+
import { useSelector } from "@xstate/store/react";
5+
6+
export const demoModeStore = createStore({
7+
// Initial context
8+
context: { demoModeUntil: undefined } as {
9+
demoModeUntil: Date | undefined
10+
},
11+
// Transitions
12+
on: {
13+
update: {
14+
demoModeUntil: (_context, event: { until: Date | undefined }) => {
15+
return event.until;
16+
},
17+
},
18+
},
19+
});
20+
21+
export const setDemoModeUntil = (until: Date) => {
22+
demoModeStore.send({
23+
type: 'update',
24+
until,
25+
})
26+
}
27+
28+
export const disableDemoMode = () => {
29+
demoModeStore.send({
30+
type: 'update',
31+
until: undefined,
32+
})
33+
}
34+
35+
export const useDemoModeStore = <T>(
36+
selector: (snapshot: SnapshotFromStore<typeof demoModeStore>) => T,
37+
) => {
38+
return useSelector(demoModeStore, selector);
39+
};
40+
41+
declare global {
42+
interface Window {
43+
demoModeStore: typeof demoModeStore;
44+
debugDemoModeStore?: boolean;
45+
}
46+
}
47+
48+
if (typeof window !== "undefined") {
49+
window.demoModeStore = demoModeStore;
50+
}
51+
52+
// window.debugQueryStore = true;
53+
demoModeStore.inspect((event) => {
54+
if (window.debugDemoModeStore) {
55+
console.log(event);
56+
}
57+
});

0 commit comments

Comments
 (0)