spaces function

This commit is contained in:
Dhravya 2024-07-01 20:12:56 -05:00
parent 6a14bea506
commit 2c6a96e96f
17 changed files with 683 additions and 71 deletions

View file

@ -3,18 +3,44 @@
import { getAllUserMemoriesAndSpaces } from "@/app/actions/fetchers";
import { Content, StoredSpace } from "@/server/db/schema";
import { MemoriesIcon, NextIcon, SearchIcon, UrlIcon } from "@repo/ui/icons";
import { NotebookIcon, PaperclipIcon } from "lucide-react";
import {
ArrowLeftIcon,
MenuIcon,
MoveIcon,
NotebookIcon,
PaperclipIcon,
TrashIcon,
} from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import React, { useEffect, useMemo, useState } from "react";
import Masonry from "react-layout-masonry";
import { getRawTweet } from "@repo/shared-types/utils";
import { MyTweet } from "./render-tweet";
import { MyTweet } from "../../../components/twitter/render-tweet";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@repo/ui/shadcn/dropdown-menu";
import { Button } from "@repo/ui/shadcn/button";
import { deleteItem, moveItem } from "@/app/actions/doers";
import { toast } from "sonner";
export function MemoriesPage({
memoriesAndSpaces,
title = "Your Memories",
currentSpace,
}: {
memoriesAndSpaces: { memories: Content[]; spaces: StoredSpace[] };
title?: string;
currentSpace?: StoredSpace;
}) {
const [filter, setFilter] = useState("All");
@ -69,9 +95,24 @@ export function MemoriesPage({
key={`${memoriesAndSpaces.memories.length + memoriesAndSpaces.spaces.length}`}
className="px-2 md:px-32 py-36 h-full flex mx-auto w-full flex-col gap-6"
>
<h2 className="text-white w-full font-medium text-3xl text-left font-semibold">
My Memories
{currentSpace && (
<Link href={"/memories"} className="flex gap-2 items-center">
<ArrowLeftIcon className="w-3 h-3" /> Back to all memories
</Link>
)}
<h2 className="text-white w-full text-3xl text-left font-semibold">
{title}
</h2>
{currentSpace && (
<div className="flex gap-4 items-center">
Space
<div className="flex items-center gap-2 bg-secondary p-2 rounded-xl">
<Image src={MemoriesIcon} alt="Spaces icon" className="w-3 h-3" />
<span className="text-[#fff]">{currentSpace.name}</span>
</div>
</div>
)}
<Filters setFilter={setFilter} filter={filter} />
@ -99,6 +140,8 @@ export function MemoriesPage({
"/placeholder-image.svg" // TODO: add this placeholder
}
description={(item.data as Content).description ?? ""}
spaces={memoriesAndSpaces.spaces}
id={(item.data as Content).id}
/>
);
}
@ -108,6 +151,7 @@ export function MemoriesPage({
<TabComponent
title={(item.data as StoredSpace).name}
description={`${(item.data as StoredSpace).numItems} memories`}
id={(item.data as StoredSpace).id}
/>
);
}
@ -122,19 +166,24 @@ export function MemoriesPage({
function TabComponent({
title,
description,
id,
}: {
title: string;
description: string;
id: number;
}) {
return (
<div className="flex flex-col gap-4 bg-[#161f2a]/30 backdrop-blur-md border-2 border-border w-full rounded-xl p-4 -z-10">
<Link
href={`/space/${id}`}
className="flex flex-col gap-4 bg-[#161f2a]/30 backdrop-blur-md border-2 border-border w-full rounded-xl p-4"
>
<div className="flex items-center gap-2 text-xs">
<Image alt="Spaces icon" src={MemoriesIcon} className="size-3" /> Space
</div>
<div className="flex items-center">
<div>
<div className="h-12 w-12 flex justify-center items-center rounded-md">
{title.slice(0, 2).toUpperCase()}
{title.slice(0, 2).toUpperCase()} {id}
</div>
</div>
<div className="grow px-4">
@ -145,7 +194,7 @@ function TabComponent({
<Image src={NextIcon} alt="Search icon" />
</div>
</div>
</div>
</Link>
);
}
@ -156,6 +205,8 @@ function LinkComponent({
url,
image,
description,
spaces,
id,
}: {
type: string;
content: string;
@ -163,34 +214,95 @@ function LinkComponent({
url: string;
image?: string;
description: string;
spaces: StoredSpace[];
id: number;
}) {
return (
<Link
href={url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
className={`bg-secondary border-2 border-border rounded-xl ${type === "tweet" ? "" : "p-4"} hover:scale-105 transition duration-200`}
<div
className={`bg-secondary group relative border-2 border-border rounded-xl ${type === "tweet" ? "" : "p-4"} hover:scale-105 transition duration-200`}
>
{type === "page" ? (
<>
<div className="flex items-center gap-2 text-xs">
<PaperclipIcon className="w-3 h-3" /> Page
</div>
<div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div>
<div>
{url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
</div>
</>
) : type === "note" ? (
<>
<div className="flex items-center gap-2 text-xs">
<NotebookIcon className="w-3 h-3" /> Note
</div>
<div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div>
<div className="line-clamp-3 mt-2">{content.replace(title, "")}</div>
</>
) : type === "tweet" ? (
<MyTweet tweet={JSON.parse(getRawTweet(content) ?? "{}")} />
) : null}
</Link>
<Link
href={url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
>
{type === "page" ? (
<>
<div className="flex items-center gap-2 text-xs">
<PaperclipIcon className="w-3 h-3" /> Page
</div>
<div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div>
<div>
{url.replace("https://supermemory.ai", "").split("#")[0] ?? "/"}
</div>
</>
) : type === "note" ? (
<>
<div className="flex items-center gap-2 text-xs">
<NotebookIcon className="w-3 h-3" /> Note
</div>
<div className="text-lg text-[#fff] mt-4 line-clamp-2">{title}</div>
<div className="line-clamp-3 mt-2">
{content.replace(title, "")}
</div>
</>
) : type === "tweet" ? (
<MyTweet tweet={JSON.parse(getRawTweet(content) ?? "{}")} />
) : null}
</Link>
<DropdownMenu modal={false}>
<DropdownMenuTrigger className="top-5 right-5 absolute opacity-0 group-focus:opacity-100 group-hover:opacity-100 transition duration-200">
<MenuIcon />
</DropdownMenuTrigger>
<DropdownMenuContent>
{spaces.length > 0 && (
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<MoveIcon className="mr-2 h-4 w-4" />
<span>Add to space</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
{spaces.map((space) => (
<DropdownMenuItem>
<button
className="w-full h-full"
onClick={async () => {
toast.info("Adding to space...");
console.log(id, space.id);
const response = await moveItem(id, [space.id]);
if (response.success) {
toast.success("Moved to space");
console.log("Moved to space");
} else {
toast.error("Failed to move to space");
console.error("Failed to move to space");
}
}}
>
{space.name}
</button>
</DropdownMenuItem>
))}
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
)}
<DropdownMenuItem asChild>
<Button
onClick={async () => {
await deleteItem(id);
}}
variant="destructive"
className="w-full"
>
<TrashIcon className="mr-2 h-4 w-4" />
Delete
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}

View file

@ -1,6 +1,6 @@
import { getAllUserMemoriesAndSpaces } from "@/app/actions/fetchers";
import { redirect } from "next/navigation";
import MemoriesPage from "./content";
import MemoriesPage from "../content";
async function Page() {
const { success, data } = await getAllUserMemoriesAndSpaces();

View file

@ -0,0 +1,17 @@
import { getMemoriesInsideSpace } from "@/app/actions/fetchers";
import { redirect } from "next/navigation";
import MemoriesPage from "../../content";
async function Page({ params: { spaceid } }: { params: { spaceid: number } }) {
const { success, data } = await getMemoriesInsideSpace(spaceid);
if (!success ?? !data) return redirect("/home");
return (
<MemoriesPage
memoriesAndSpaces={{ memories: data.memories, spaces: [] }}
title={data.spaces[0]?.name}
currentSpace={data.spaces[0]}
/>
);
}
export default Page;

View file

@ -1,33 +0,0 @@
import type { Tweet } from "react-tweet/api";
import {
type TwitterComponents,
TweetContainer,
TweetHeader,
TweetInReplyTo,
TweetBody,
TweetMedia,
TweetInfo,
QuotedTweet,
enrichTweet,
} from "react-tweet";
type Props = {
tweet: Tweet;
components?: TwitterComponents;
};
export const MyTweet = ({ tweet: t, components }: Props) => {
const tweet = enrichTweet(t);
return (
<TweetContainer className="bg-transparent !m-0 !p-0 !z-0">
<TweetHeader tweet={tweet} components={components} />
{tweet.in_reply_to_status_id_str && <TweetInReplyTo tweet={tweet} />}
<TweetBody tweet={tweet} />
{tweet.mediaDetails?.length ? (
<TweetMedia tweet={tweet} components={components} />
) : null}
{tweet.quoted_tweet && <QuotedTweet tweet={tweet.quoted_tweet} />}
<TweetInfo tweet={tweet} />
</TweetContainer>
);
};

View file

@ -191,7 +191,8 @@ export const createMemory = async (input: {
storeToSpaces = [];
}
console.log(storeToSpaces);
console.log("SAVING URL: ", metadata.baseUrl);
const vectorSaveResponse = await fetch(
`${process.env.BACKEND_BASE_URL}/api/add`,
{
@ -298,10 +299,12 @@ export const createMemory = async (input: {
.all();
await Promise.all(
spaceData.map(async (space) => {
spaceData.map(async (s) => {
await db
.insert(contentToSpace)
.values({ contentId: contentId, spaceId: space.id });
.values({ contentId: contentId, spaceId: s.id });
db.update(space).set({ numItems: s.numItems + 1 });
}),
);
}
@ -437,6 +440,124 @@ export const linkTelegramToUser = async (
};
};
export const deleteItem = async (id: number) => {
const data = await auth();
if (!data || !data.user || !data.user.id) {
redirect("/signin");
return { error: "Not authenticated", success: false };
}
try {
const deletedItem = await db
.delete(storedContent)
.where(eq(storedContent.id, id))
.returning();
if (!deletedItem) {
return {
success: false,
error: "Failed to delete item",
};
}
const actualUrl = deletedItem[0]?.url.split("#supermemory-user-")[0];
console.log(
"ACTUAL URL BADBAL;KFJDLKASJFLKDSJFLKDSJFKD LSFJSLKDJF :",
actualUrl,
);
await fetch(`${process.env.BACKEND_BASE_URL}/api/delete`, {
method: "POST",
body: JSON.stringify({
websiteUrl: actualUrl,
user: data.user.id,
}),
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY,
},
});
revalidatePath("/memories");
return {
success: true,
message: "in-sync",
};
} catch (error) {
return {
success: false,
error,
message: "An error occured while saving your canvas",
};
}
};
// TODO: also move in vectorize
export const moveItem = async (
id: number,
spaces: number[],
fromSpace?: number | undefined,
): ServerActionReturnType<boolean> => {
const data = await auth();
if (!data || !data.user || !data.user.id) {
redirect("/signin");
return { error: "Not authenticated", success: false };
}
try {
if (fromSpace) {
await db
.delete(contentToSpace)
.where(
and(
eq(contentToSpace.contentId, id),
eq(contentToSpace.spaceId, fromSpace),
),
);
}
const addedItem = await db
.insert(contentToSpace)
.values(spaces.map((spaceId) => ({ contentId: id, spaceId })))
.returning();
if (!(addedItem.length > 0)) {
return {
success: false,
error: "Failed to move item",
};
}
await db
.update(space)
.set({ numItems: sql<number>`numItems + 1` })
.where(eq(space.id, addedItem[0]?.spaceId!));
if (!addedItem) {
return {
success: false,
error: "Failed to move item",
};
}
revalidatePath("/memories");
return {
success: true,
data: true,
};
} catch (error) {
return {
success: false,
error: (error as Error).message,
};
}
};
export const createCanvas = async () => {
const data = await auth();

View file

@ -9,6 +9,7 @@ import {
chatThreads,
Content,
contentToSpace,
space,
storedContent,
StoredSpace,
users,
@ -75,6 +76,43 @@ export const getAllMemories = async (
return { success: true, data: contentNotInAnySpace };
};
export const getMemoriesInsideSpace = async (
spaceId: number,
): ServerActionReturnType<{ memories: Content[]; spaces: StoredSpace[] }> => {
const data = await auth();
if (!data || !data.user) {
redirect("/signin");
return { error: "Not authenticated", success: false };
}
const memories = await db
.select()
.from(storedContent)
.where(
inArray(
storedContent.id,
db
.select({ contentId: contentToSpace.contentId })
.from(contentToSpace)
.where(eq(contentToSpace.spaceId, spaceId)),
),
)
.execute();
const queriedSpace = await db.query.space.findFirst({
where: and(eq(users, data.user.id), eq(space.id, spaceId)),
});
return {
success: true,
data: {
memories: memories,
spaces: queriedSpace ? [queriedSpace] : [],
},
};
};
export const getAllUserMemoriesAndSpaces = async (): ServerActionReturnType<{
spaces: StoredSpace[];
memories: Content[];

View file

@ -128,10 +128,12 @@ const createMemoryFromAPI = async (input: {
.all();
await Promise.all(
spaceData.map(async (space) => {
spaceData.map(async (s) => {
await db
.insert(contentToSpace)
.values({ contentId: contentId, spaceId: space.id });
.values({ contentId: contentId, spaceId: s.id });
await db.update(space).set({ numItems: s.numItems + 1 });
}),
);
}

View file

@ -0,0 +1,9 @@
.verified {
margin-left: 0.125rem;
max-width: 20px;
max-height: 20px;
height: 1.25em;
fill: currentColor;
user-select: none;
vertical-align: text-bottom;
}

View file

@ -0,0 +1,3 @@
export * from "./verified";
export * from "./verified-business";
export * from "./verified-government";

View file

@ -0,0 +1,53 @@
import s from "./icons.module.css";
export const VerifiedBusiness = () => (
<svg
viewBox="0 0 22 22"
aria-label="Verified account"
role="img"
className={s.verified}
>
<g>
<linearGradient
gradientUnits="userSpaceOnUse"
id="0-a"
x1="4.411"
x2="18.083"
y1="2.495"
y2="21.508"
>
<stop offset="0" stopColor="#f4e72a"></stop>
<stop offset=".539" stopColor="#cd8105"></stop>
<stop offset=".68" stopColor="#cb7b00"></stop>
<stop offset="1" stopColor="#f4ec26"></stop>
<stop offset="1" stopColor="#f4e72a"></stop>
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
id="0-b"
x1="5.355"
x2="16.361"
y1="3.395"
y2="19.133"
>
<stop offset="0" stopColor="#f9e87f"></stop>
<stop offset=".406" stopColor="#e2b719"></stop>
<stop offset=".989" stopColor="#e2b719"></stop>
</linearGradient>
<g clipRule="evenodd" fillRule="evenodd">
<path
d="M13.324 3.848L11 1.6 8.676 3.848l-3.201-.453-.559 3.184L2.06 8.095 3.48 11l-1.42 2.904 2.856 1.516.559 3.184 3.201-.452L11 20.4l2.324-2.248 3.201.452.559-3.184 2.856-1.516L18.52 11l1.42-2.905-2.856-1.516-.559-3.184zm-7.09 7.575l3.428 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
fill="url(#0-a)"
></path>
<path
d="M13.101 4.533L11 2.5 8.899 4.533l-2.895-.41-.505 2.88-2.583 1.37L4.2 11l-1.284 2.627 2.583 1.37.505 2.88 2.895-.41L11 19.5l2.101-2.033 2.895.41.505-2.88 2.583-1.37L17.8 11l1.284-2.627-2.583-1.37-.505-2.88zm-6.868 6.89l3.429 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
fill="url(#0-b)"
></path>
<path
d="M6.233 11.423l3.429 3.428 5.65-6.17.038-.033-.005 1.398-5.683 6.206-3.429-3.429-.003-1.405.005.003z"
fill="#d18800"
></path>
</g>
</g>
</svg>
);

View file

@ -0,0 +1,18 @@
import s from "./icons.module.css";
export const VerifiedGovernment = () => (
<svg
viewBox="0 0 22 22"
aria-label="Verified account"
role="img"
className={s.verified}
>
<g>
<path
clipRule="evenodd"
d="M12.05 2.056c-.568-.608-1.532-.608-2.1 0l-1.393 1.49c-.284.303-.685.47-1.1.455L5.42 3.932c-.832-.028-1.514.654-1.486 1.486l.069 2.039c.014.415-.152.816-.456 1.1l-1.49 1.392c-.608.568-.608 1.533 0 2.101l1.49 1.393c.304.284.47.684.456 1.1l-.07 2.038c-.027.832.655 1.514 1.487 1.486l2.038-.069c.415-.014.816.152 1.1.455l1.392 1.49c.569.609 1.533.609 2.102 0l1.393-1.49c.283-.303.684-.47 1.099-.455l2.038.069c.832.028 1.515-.654 1.486-1.486L18 14.542c-.015-.415.152-.815.455-1.099l1.49-1.393c.608-.568.608-1.533 0-2.101l-1.49-1.393c-.303-.283-.47-.684-.455-1.1l.068-2.038c.029-.832-.654-1.514-1.486-1.486l-2.038.07c-.415.013-.816-.153-1.1-.456zm-5.817 9.367l3.429 3.428 5.683-6.206-1.347-1.247-4.4 4.795-2.072-2.072z"
fillRule="evenodd"
></path>
</g>
</svg>
);

View file

@ -0,0 +1,14 @@
import s from "./icons.module.css";
export const Verified = () => (
<svg
viewBox="0 0 24 24"
aria-label="Verified account"
role="img"
className={s.verified}
>
<g>
<path d="M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.33 2.19c-1.4-.46-2.91-.2-3.92.81s-1.26 2.52-.8 3.91c-1.31.67-2.2 1.91-2.2 3.34s.89 2.67 2.2 3.34c-.46 1.39-.21 2.9.8 3.91s2.52 1.26 3.91.81c.67 1.31 1.91 2.19 3.34 2.19s2.68-.88 3.34-2.19c1.39.45 2.9.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z"></path>
</g>
</svg>
);

View file

@ -0,0 +1,117 @@
import type { Tweet } from "react-tweet/api";
import {
type TwitterComponents,
TweetContainer,
TweetInReplyTo,
TweetBody,
TweetMedia,
TweetInfo,
QuotedTweet,
enrichTweet,
EnrichedTweet,
} from "react-tweet";
import clsx from "clsx";
import s from "./tweet-header.module.css";
import { VerifiedBadge } from "./verified-badge";
type Props = {
tweet: Tweet;
components?: TwitterComponents;
};
type AvatarImgProps = {
src: string;
alt: string;
width: number;
height: number;
};
const AvatarImg = (props: AvatarImgProps) => <img {...props} />;
const TweetHeader = ({
tweet,
components,
}: {
tweet: EnrichedTweet;
components?: TwitterComponents;
}) => {
const Img = components?.AvatarImg ?? AvatarImg;
const { user } = tweet;
return (
<div className={s.header}>
<a
href={tweet.url}
className={s.avatar}
target="_blank"
rel="noopener noreferrer"
>
<div
className={clsx(
s.avatarOverflow,
user.profile_image_shape === "Square" && s.avatarSquare,
)}
>
<Img
src={user.profile_image_url_https}
alt={user.name}
width={48}
height={48}
/>
</div>
<div className={s.avatarOverflow}>
<div className={s.avatarShadow}></div>
</div>
</a>
<div className={s.author}>
<a
href={tweet.url}
className={s.authorLink}
target="_blank"
rel="noopener noreferrer"
>
<div className={s.authorLinkText}>
<span title={user.name}>{user.name}</span>
</div>
<VerifiedBadge user={user} className={s.authorVerified} />
</a>
<div className={s.authorMeta}>
<a
href={tweet.url}
className={s.username}
target="_blank"
rel="noopener noreferrer"
>
<span title={`@${user.screen_name}`}>@{user.screen_name}</span>
</a>
<div className={s.authorFollow}>
<span className={s.separator}>·</span>
<a
href={user.follow_url}
className={s.follow}
target="_blank"
rel="noopener noreferrer"
>
Follow
</a>
</div>
</div>
</div>
</div>
);
};
export const MyTweet = ({ tweet: t, components }: Props) => {
const tweet = enrichTweet(t);
return (
<TweetContainer className="bg-transparent !m-0 !p-0 !z-0">
<TweetHeader tweet={tweet} components={components} />
{tweet.in_reply_to_status_id_str && <TweetInReplyTo tweet={tweet} />}
<TweetBody tweet={tweet} />
{tweet.mediaDetails?.length ? (
<TweetMedia tweet={tweet} components={components} />
) : null}
{tweet.quoted_tweet && <QuotedTweet tweet={tweet.quoted_tweet} />}
<TweetInfo tweet={tweet} />
</TweetContainer>
);
};

View file

@ -0,0 +1,96 @@
.header {
display: flex;
padding-bottom: 0.75rem;
line-height: var(--tweet-header-line-height);
font-size: var(--tweet-header-font-size);
white-space: nowrap;
overflow-wrap: break-word;
overflow: hidden;
}
.avatar {
position: relative;
height: 48px;
width: 48px;
}
.avatarOverflow {
height: 100%;
width: 100%;
position: absolute;
overflow: hidden;
border-radius: 9999px;
}
.avatarSquare {
border-radius: 4px;
}
.avatarShadow {
height: 100%;
width: 100%;
transition-property: background-color;
transition-duration: 0.2s;
box-shadow: rgb(0 0 0 / 3%) 0px 0px 2px inset;
}
.avatarShadow:hover {
background-color: rgba(26, 26, 26, 0.15);
}
.author {
max-width: calc(100% - 84px);
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 0.5rem;
}
.authorLink {
text-decoration: none;
color: inherit;
display: flex;
align-items: center;
}
.authorLink:hover {
text-decoration-line: underline;
}
.authorVerified {
display: inline-flex;
}
.authorLinkText {
font-weight: 700;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.authorMeta {
display: flex;
}
.authorFollow {
display: flex;
}
.username {
color: var(--tweet-font-color-secondary);
text-decoration: none;
text-overflow: ellipsis;
}
.follow {
color: var(--tweet-color-blue-secondary);
text-decoration: none;
font-weight: 700;
}
.follow:hover {
text-decoration-line: underline;
}
.separator {
padding: 0 0.25rem;
}
.brand {
margin-inline-start: auto;
}
.twitterIcon {
width: 23.75px;
height: 23.75px;
color: var(--tweet-twitter-icon-color);
fill: currentColor;
user-select: none;
}

View file

@ -0,0 +1,10 @@
.verifiedOld {
color: var(--tweet-verified-old-color);
}
.verifiedBlue {
color: var(--tweet-verified-blue-color);
}
.verifiedGovernment {
/* color: var(--tweet-verified-government-color); */
color: rgb(130, 154, 171);
}

View file

@ -0,0 +1,34 @@
import clsx from "clsx";
import { Verified, VerifiedBusiness, VerifiedGovernment } from "./icons/index";
import s from "./verified-badge.module.css";
type Props = {
user: any;
className?: string;
};
export const VerifiedBadge = ({ user, className }: Props) => {
const verified = user.verified || user.is_blue_verified || user.verified_type;
let icon = <Verified />;
let iconClassName: string | null = s.verifiedBlue ?? null;
if (verified) {
if (!user.is_blue_verified) {
iconClassName = s.verifiedOld!;
}
switch (user.verified_type) {
case "Government":
icon = <VerifiedGovernment />;
iconClassName = s.verifiedGovernment!;
break;
case "Business":
icon = <VerifiedBusiness />;
iconClassName = null;
break;
}
}
return verified ? (
<div className={clsx(className, iconClassName)}>{icon}</div>
) : null;
};

View file

@ -57,6 +57,7 @@
"@million/lint": "^1.0.0-rc.18",
"@mozilla/readability": "^0.5.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.1.1",