1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
"use client";
import { ArrowRightIcon } from "lucide-react";
import { useState } from "react";
import ReactMarkdown from "react-markdown";
import { useLocalStorage } from "@/services/storage/use-local-storage";
import { Button } from "../ui/button";
import { Dialog, DialogBody, DialogContent, DialogTitle } from "../ui/dialog";
export function Onboarding() {
const [step, setStep] = useState(0);
const [hasSeenOnboarding, setHasSeenOnboarding] = useLocalStorage({
key: "hasSeenOnboarding",
defaultValue: false,
});
const isOpen = !hasSeenOnboarding;
const handleNext = () => {
setStep(step + 1);
};
const handleClose = () => {
setHasSeenOnboarding({ value: true });
};
const getStepTitle = () => {
switch (step) {
case 0:
return "Welcome to OpenCut Beta! 🎉";
case 1:
return "⚠️ This is a super early beta!";
case 2:
return "🦋 Have fun testing!";
default:
return "OpenCut Onboarding";
}
};
const renderStepContent = () => {
switch (step) {
case 0:
return (
<div className="space-y-5">
<div className="space-y-3">
<Title title="Welcome to OpenCut Beta! 🎉" />
<Description description="You're among the first to try OpenCut - the fully open source CapCut alternative." />
</div>
<NextButton onClick={handleNext}>Next</NextButton>
</div>
);
case 1:
return (
<div className="space-y-5">
<div className="space-y-3">
<Title title={getStepTitle()} />
<Description description="There's still a ton of things to do to make this editor amazing." />
<Description description="A lot of features are still missing. We're working hard to build them out!" />
<Description description="If you're curious, check out our roadmap [here](https://opencut.app/roadmap)" />
</div>
<NextButton onClick={handleNext}>Next</NextButton>
</div>
);
case 2:
return (
<div className="space-y-5">
<div className="space-y-3">
<Title title={getStepTitle()} />
<Description description="Have fun testing! Your projects and media stay local in your browser." />
</div>
<NextButton onClick={handleClose}>Finish</NextButton>
</div>
);
default:
return null;
}
};
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[425px]">
<DialogTitle>
<span className="sr-only">{getStepTitle()}</span>
</DialogTitle>
<DialogBody>{renderStepContent()}</DialogBody>
</DialogContent>
</Dialog>
);
}
function Title({ title }: { title: string }) {
return <h2 className="text-lg font-bold md:text-xl">{title}</h2>;
}
function Description({ description }: { description: string }) {
return (
<div className="text-muted-foreground">
<ReactMarkdown
components={{
p: ({ children }) => <p className="mb-0">{children}</p>,
a: ({ href, children }) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="text-foreground hover:text-foreground/80 underline"
>
{children}
</a>
),
}}
>
{description}
</ReactMarkdown>
</div>
);
}
function NextButton({
children,
onClick,
}: {
children: React.ReactNode;
onClick: () => void;
}) {
return (
<Button onClick={onClick} variant="default" className="w-full">
{children}
<ArrowRightIcon className="size-4" />
</Button>
);
}
|