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,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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user