Self-hosted dashboard and admin auth (#4837)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
co-authored by
kartik-mem0
parent
15feaa8ac4
commit
db8ac61713
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
interface EmptyStateProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
image?: "memories" | "requests";
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function EmptyState({
|
||||
title,
|
||||
description,
|
||||
image = "memories",
|
||||
children,
|
||||
}: EmptyStateProps) {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const src =
|
||||
resolvedTheme === "dark"
|
||||
? `/images/no-${image}-dark.svg`
|
||||
: `/images/no-${image}.svg`;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-16 text-center">
|
||||
<Image
|
||||
src={src}
|
||||
alt=""
|
||||
width={120}
|
||||
height={120}
|
||||
className="mb-4 opacity-80"
|
||||
/>
|
||||
<p className="text-sm font-medium text-onSurface-default-primary">
|
||||
{title}
|
||||
</p>
|
||||
{description && (
|
||||
<p className="text-xs text-onSurface-default-tertiary mt-1 max-w-xs">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Lock } from "lucide-react";
|
||||
|
||||
interface LockedPageProps {
|
||||
title: string;
|
||||
description: string;
|
||||
previewContent: React.ReactNode;
|
||||
utmMedium: string;
|
||||
}
|
||||
|
||||
export function LockedPage({
|
||||
title,
|
||||
description,
|
||||
previewContent,
|
||||
utmMedium,
|
||||
}: LockedPageProps) {
|
||||
const utm = `utm_source=oss&utm_medium=${utmMedium}`;
|
||||
const cloudUrl = `https://app.mem0.ai?${utm}`;
|
||||
const salesUrl = `https://app.mem0.ai/enterprise?${utm}`;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold font-fustat flex items-center gap-2">
|
||||
{title}
|
||||
<Lock className="size-4 text-onSurface-default-tertiary" />
|
||||
</h1>
|
||||
<p className="text-sm text-onSurface-default-secondary mt-1">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="opacity-60 pointer-events-none select-none">
|
||||
{previewContent}
|
||||
</div>
|
||||
|
||||
<Card className="border-memBorder-primary">
|
||||
<CardContent className="flex flex-col sm:flex-row items-center gap-4 py-6">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">
|
||||
This feature is available in Mem0 Cloud and Enterprise.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<Button variant="default" asChild>
|
||||
<a href={cloudUrl} target="_blank" rel="noopener noreferrer">
|
||||
Start free on Cloud
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="outline" asChild>
|
||||
<a href={salesUrl} target="_blank" rel="noopener noreferrer">
|
||||
Talk to sales
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface UpgradeBannerProps {
|
||||
id: string;
|
||||
message: string;
|
||||
ctaLabel: string;
|
||||
ctaUrl: string;
|
||||
variant: "cloud" | "enterprise";
|
||||
dismissible?: boolean;
|
||||
}
|
||||
|
||||
export function UpgradeBanner({
|
||||
id,
|
||||
message,
|
||||
ctaLabel,
|
||||
ctaUrl,
|
||||
variant,
|
||||
dismissible = true,
|
||||
}: UpgradeBannerProps) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setDismissed(localStorage.getItem(`nudge_${id}_dismissed`) === "true");
|
||||
setMounted(true);
|
||||
}, [id]);
|
||||
|
||||
if (!mounted || dismissed) return null;
|
||||
|
||||
const handleDismiss = () => {
|
||||
localStorage.setItem(`nudge_${id}_dismissed`, "true");
|
||||
setDismissed(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-4 py-2.5 rounded-md typo-body-xs font-fustat",
|
||||
variant === "cloud"
|
||||
? "bg-memGold-100 border-l-2 border-memGold-500"
|
||||
: "bg-memRed-100 border-l-2 border-memRed-400",
|
||||
)}
|
||||
>
|
||||
<p className="flex-1 text-onSurface-default-secondary">{message}</p>
|
||||
<a
|
||||
href={ctaUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={cn(
|
||||
"text-xs font-medium whitespace-nowrap underline",
|
||||
variant === "cloud" ? "text-memGold-700" : "text-memRed-600",
|
||||
)}
|
||||
>
|
||||
{ctaLabel}
|
||||
</a>
|
||||
{dismissible && (
|
||||
<button
|
||||
onClick={handleDismiss}
|
||||
className="p-0.5 rounded hover:bg-surface-default-secondary-hover text-onSurface-default-tertiary"
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user