Self-hosted dashboard and admin auth (#4837)

Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
Gabriel Stein
2026-04-23 19:06:36 +05:30
committed by GitHub
co-authored by kartik-mem0
parent 15feaa8ac4
commit db8ac61713
252 changed files with 20275 additions and 331 deletions
@@ -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>
);
}