aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web
diff options
context:
space:
mode:
authorMohamedBassem <me@mbassem.com>2024-03-19 12:41:06 +0000
committerMohamedBassem <me@mbassem.com>2024-03-19 12:44:12 +0000
commit64fb87d0ceb591a49aacad39bf5e885cab7decfa (patch)
treeda60a56a0f6546d20d753519e525e9259f789e9c /apps/web
parentf94432b94512cef90cca6d0d5396b4547ccbe68b (diff)
downloadkarakeep-64fb87d0ceb591a49aacad39bf5e885cab7decfa.tar.zst
feature(web): Add support for demo mode
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/app/dashboard/layout.tsx2
-rw-r--r--apps/web/app/layout.tsx4
-rw-r--r--apps/web/components/DemoModeBanner.tsx7
-rw-r--r--apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx7
-rw-r--r--apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx1
-rw-r--r--apps/web/components/dashboard/bookmarks/TagsEditor.tsx3
-rw-r--r--apps/web/components/dashboard/sidebar/NewListModal.tsx17
-rw-r--r--apps/web/components/signin/CredentialsForm.tsx6
-rw-r--r--apps/web/components/ui/action-button.tsx9
-rw-r--r--apps/web/lib/clientConfig.tsx11
-rw-r--r--apps/web/lib/providers.tsx21
11 files changed, 74 insertions, 14 deletions
diff --git a/apps/web/app/dashboard/layout.tsx b/apps/web/app/dashboard/layout.tsx
index 27e06955..b79fd0f9 100644
--- a/apps/web/app/dashboard/layout.tsx
+++ b/apps/web/app/dashboard/layout.tsx
@@ -1,6 +1,7 @@
import MobileSidebar from "@/components/dashboard/sidebar/ModileSidebar";
import Sidebar from "@/components/dashboard/sidebar/Sidebar";
import UploadDropzone from "@/components/dashboard/UploadDropzone";
+import DemoModeBanner from "@/components/DemoModeBanner";
import { Separator } from "@/components/ui/separator";
export default async function Dashboard({
@@ -14,6 +15,7 @@ export default async function Dashboard({
<Sidebar />
</div>
<main className="flex-1 bg-gray-100 sm:overflow-y-auto">
+ <DemoModeBanner />
<div className="block w-full sm:hidden">
<MobileSidebar />
<Separator />
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 6ec9c3e4..395d7297 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -10,6 +10,8 @@ import Providers from "@/lib/providers";
import { getServerAuthSession } from "@/server/auth";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+import { clientConfig } from "@hoarder/shared/config";
+
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
@@ -42,7 +44,7 @@ export default async function RootLayout({
return (
<html lang="en">
<body className={inter.className}>
- <Providers session={session}>
+ <Providers session={session} clientConfig={clientConfig}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</Providers>
diff --git a/apps/web/components/DemoModeBanner.tsx b/apps/web/components/DemoModeBanner.tsx
new file mode 100644
index 00000000..6250be87
--- /dev/null
+++ b/apps/web/components/DemoModeBanner.tsx
@@ -0,0 +1,7 @@
+export default function DemoModeBanner() {
+ return (
+ <div className="h-min w-full rounded bg-yellow-100 px-4 py-2 text-center">
+ Demo mode is on. All modifications are disabled.
+ </div>
+ );
+}
diff --git a/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx b/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx
index 3656a435..692d7d78 100644
--- a/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx
+++ b/apps/web/components/dashboard/bookmarks/BookmarkOptions.tsx
@@ -9,6 +9,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useToast } from "@/components/ui/use-toast";
+import { useClientConfig } from "@/lib/clientConfig";
import { api } from "@/lib/trpc";
import {
Archive,
@@ -32,6 +33,8 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
const { toast } = useToast();
const linkId = bookmark.id;
+ const demoMode = useClientConfig().demoMode;
+
const { setOpen: setTagModalIsOpen, content: tagModal } =
useTagModel(bookmark);
const { setOpen: setAddToListModalOpen, content: addToListModal } =
@@ -115,6 +118,7 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
</DropdownMenuItem>
)}
<DropdownMenuItem
+ disabled={demoMode}
onClick={() =>
updateBookmarkMutator.mutate({
bookmarkId: linkId,
@@ -126,6 +130,7 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
<span>{bookmark.favourited ? "Un-favourite" : "Favourite"}</span>
</DropdownMenuItem>
<DropdownMenuItem
+ disabled={demoMode}
onClick={() =>
updateBookmarkMutator.mutate({
bookmarkId: linkId,
@@ -163,6 +168,7 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
{bookmark.content.type === "link" && (
<DropdownMenuItem
+ disabled={demoMode}
onClick={() =>
crawlBookmarkMutator.mutate({ bookmarkId: bookmark.id })
}
@@ -172,6 +178,7 @@ export default function BookmarkOptions({ bookmark }: { bookmark: ZBookmark }) {
</DropdownMenuItem>
)}
<DropdownMenuItem
+ disabled={demoMode}
className="text-destructive"
onClick={() =>
deleteBookmarkMutator.mutate({ bookmarkId: bookmark.id })
diff --git a/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx b/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx
index b40e6e42..4b0dc4fd 100644
--- a/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx
+++ b/apps/web/components/dashboard/bookmarks/BookmarksGrid.tsx
@@ -92,6 +92,7 @@ export default function BookmarksGrid({
</Masonry>
{hasNextPage && (
<ActionButton
+ ignoreDemoMode={true}
loading={isFetchingNextPage}
onClick={() => fetchNextPage()}
className="mx-auto w-min"
diff --git a/apps/web/components/dashboard/bookmarks/TagsEditor.tsx b/apps/web/components/dashboard/bookmarks/TagsEditor.tsx
index 38f01bdd..4cccfc02 100644
--- a/apps/web/components/dashboard/bookmarks/TagsEditor.tsx
+++ b/apps/web/components/dashboard/bookmarks/TagsEditor.tsx
@@ -1,5 +1,6 @@
import type { ActionMeta } from "react-select";
import { toast } from "@/components/ui/use-toast";
+import { useClientConfig } from "@/lib/clientConfig";
import { api } from "@/lib/trpc";
import { cn } from "@/lib/utils";
import { Sparkles } from "lucide-react";
@@ -15,6 +16,7 @@ interface EditableTag {
}
export function TagsEditor({ bookmark }: { bookmark: ZBookmark }) {
+ const demoMode = useClientConfig().demoMode;
const bookmarkInvalidationFunction =
api.useUtils().bookmarks.getBookmark.invalidate;
@@ -79,6 +81,7 @@ export function TagsEditor({ bookmark }: { bookmark: ZBookmark }) {
return (
<CreateableSelect
+ isDisabled={demoMode}
onChange={onChange}
options={
existingTags?.tags.map((t) => ({
diff --git a/apps/web/components/dashboard/sidebar/NewListModal.tsx b/apps/web/components/dashboard/sidebar/NewListModal.tsx
index e244411d..31c35d6c 100644
--- a/apps/web/components/dashboard/sidebar/NewListModal.tsx
+++ b/apps/web/components/dashboard/sidebar/NewListModal.tsx
@@ -67,10 +67,19 @@ export default function NewListModal() {
},
onError: (e) => {
if (e.data?.code == "BAD_REQUEST") {
- toast({
- variant: "destructive",
- description: e.message,
- });
+ if (e.data.zodError) {
+ toast({
+ variant: "destructive",
+ description: Object.values(e.data.zodError.fieldErrors)
+ .flat()
+ .join("\n"),
+ });
+ } else {
+ toast({
+ variant: "destructive",
+ description: e.message,
+ });
+ }
} else {
toast({
variant: "destructive",
diff --git a/apps/web/components/signin/CredentialsForm.tsx b/apps/web/components/signin/CredentialsForm.tsx
index 59dfeb21..8e1423eb 100644
--- a/apps/web/components/signin/CredentialsForm.tsx
+++ b/apps/web/components/signin/CredentialsForm.tsx
@@ -86,7 +86,11 @@ function SignIn() {
);
}}
/>
- <ActionButton type="submit" loading={form.formState.isSubmitting}>
+ <ActionButton
+ ignoreDemoMode
+ type="submit"
+ loading={form.formState.isSubmitting}
+ >
Sign In
</ActionButton>
</div>
diff --git a/apps/web/components/ui/action-button.tsx b/apps/web/components/ui/action-button.tsx
index 11b02a5f..5b862e07 100644
--- a/apps/web/components/ui/action-button.tsx
+++ b/apps/web/components/ui/action-button.tsx
@@ -1,3 +1,5 @@
+import { useClientConfig } from "@/lib/clientConfig";
+
import type { ButtonProps } from "./button";
import { Button } from "./button";
import LoadingSpinner from "./spinner";
@@ -7,13 +9,18 @@ export function ActionButton({
loading,
spinner,
disabled,
+ ignoreDemoMode = false,
...props
}: ButtonProps & {
loading: boolean;
spinner?: React.ReactNode;
+ ignoreDemoMode?: boolean;
}) {
+ const clientConfig = useClientConfig();
spinner ||= <LoadingSpinner />;
- if (disabled !== undefined) {
+ if (!ignoreDemoMode && clientConfig.demoMode) {
+ disabled = true;
+ } else if (disabled !== undefined) {
disabled ||= loading;
} else if (loading) {
disabled = true;
diff --git a/apps/web/lib/clientConfig.tsx b/apps/web/lib/clientConfig.tsx
new file mode 100644
index 00000000..fac76d3b
--- /dev/null
+++ b/apps/web/lib/clientConfig.tsx
@@ -0,0 +1,11 @@
+import { createContext, useContext } from "react";
+
+import type { ClientConfig } from "@hoarder/shared/config";
+
+export const ClientConfigCtx = createContext<ClientConfig>({
+ demoMode: false,
+});
+
+export function useClientConfig() {
+ return useContext(ClientConfigCtx);
+}
diff --git a/apps/web/lib/providers.tsx b/apps/web/lib/providers.tsx
index db51c361..ce667f8d 100644
--- a/apps/web/lib/providers.tsx
+++ b/apps/web/lib/providers.tsx
@@ -7,6 +7,9 @@ import { httpBatchLink, loggerLink } from "@trpc/client";
import { SessionProvider } from "next-auth/react";
import superjson from "superjson";
+import type { ClientConfig } from "@hoarder/shared/config";
+
+import { ClientConfigCtx } from "./clientConfig";
import { api } from "./trpc";
function makeQueryClient() {
@@ -40,9 +43,11 @@ function getQueryClient() {
export default function Providers({
children,
session,
+ clientConfig,
}: {
children: React.ReactNode;
session: Session | null;
+ clientConfig: ClientConfig;
}) {
const queryClient = getQueryClient();
@@ -64,12 +69,14 @@ export default function Providers({
);
return (
- <SessionProvider session={session}>
- <api.Provider client={trpcClient} queryClient={queryClient}>
- <QueryClientProvider client={queryClient}>
- {children}
- </QueryClientProvider>
- </api.Provider>
- </SessionProvider>
+ <ClientConfigCtx.Provider value={clientConfig}>
+ <SessionProvider session={session}>
+ <api.Provider client={trpcClient} queryClient={queryClient}>
+ <QueryClientProvider client={queryClient}>
+ {children}
+ </QueryClientProvider>
+ </api.Provider>
+ </SessionProvider>
+ </ClientConfigCtx.Provider>
);
}