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,36 @@
import { useState } from "react";
import { CheckIcon, CopyIcon } from "@radix-ui/react-icons";
import { CopyToClipboard } from "react-copy-to-clipboard";
interface CopyButtonProps {
textToCopy: string;
}
const CopyButton: React.FC<CopyButtonProps> = ({ textToCopy }) => {
const [copied, setCopied] = useState(false);
const handleCopy = () => {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
};
return (
<CopyToClipboard text={textToCopy} onCopy={handleCopy}>
<button
type="button"
className="absolute top-2 right-2 bg-white hover:bg-gray-100 p-2 text-black rounded-md"
>
{copied ? (
<CheckIcon className="size-3" />
) : (
<CopyIcon className="size-3" />
)}
</button>
</CopyToClipboard>
);
};
export default CopyButton;
@@ -0,0 +1,10 @@
import React from "react";
export function Spinner({ size = "small", className = "" }) {
const spinnerSize = size === "small" ? "w-4 h-4" : "w-6 h-6";
return (
<div
className={`${spinnerSize} ${className} animate-spin rounded-full border-y-2 border-memBorder-primary`}
></div>
);
}
@@ -0,0 +1,30 @@
"use client";
import React, { useEffect, useState } from "react";
import Image from "next/image";
import { useTheme } from "next-themes";
export default function ThemeAwareLogo({
width = 120,
height = 40,
}: {
width?: number;
height?: number;
}) {
const [mounted, setMounted] = useState(false);
const { theme, resolvedTheme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return <div style={{ width, height }} />;
}
const currentTheme = theme === "system" ? resolvedTheme : theme;
const logoSrc =
currentTheme === "dark" ? "/images/dark.svg" : "/images/light.svg";
return <Image src={logoSrc} alt="Mem0.ai" width={width} height={height} />;
}
@@ -0,0 +1,38 @@
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { createPortal } from "react-dom";
export const TruncatedText: React.FC<{ text: string; limit?: number }> = ({
text,
limit = 100,
}) => {
if (!text) return null;
if (text.length <= limit) return <span>{text}</span>;
return (
<HoverCard>
<HoverCardTrigger asChild>
<span className="cursor-pointer">{text?.slice(0, limit)}...</span>
</HoverCardTrigger>
{createPortal(
<HoverCardContent
className="text-foreground p-4 shadow-lg rounded-lg z-[9999]"
style={{
minWidth: "400px",
maxWidth: "600px",
width: "auto", // Allows it to adapt based on content
transform: "translateY(10px)",
whiteSpace: "normal", // Wraps text to prevent overflow
overflowWrap: "break-word", // Breaks long words to fit within max-width
}}
>
<p className="">{text}</p>
</HoverCardContent>,
document.body,
)}
</HoverCard>
);
};