Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add avatar for visitors #263

Merged
merged 1 commit into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions components/links/links-visitors.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { durationFormat, timeAgo } from "@/lib/utils";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { TableCell, TableRow } from "@/components/ui/table";
import { useLinkVisits } from "@/lib/swr/use-link";
import { Gauge } from "@/components/ui/gauge";
import { VisitorAvatar } from "@/components/visitors/visitor-avatar";

export default function LinksVisitors({
linkId,
Expand All @@ -23,11 +23,7 @@ export default function LinksVisitors({
<div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3 overflow-visible w-[220px]">
<Avatar className="flex-shrink-0 hidden sm:inline-flex">
<AvatarFallback>
{view.viewerEmail?.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<VisitorAvatar viewerEmail={view.viewerEmail} />
<div className="min-w-0 flex-1">
<div className="focus:outline-none">
<p className="text-sm text-gray-800 dark:text-gray-200 overflow-visible">
Expand Down Expand Up @@ -60,11 +56,7 @@ export default function LinksVisitors({
<div>
<div className="flex items-center justify-between">
<div className="flex items-center truncate w-[220px]">
<Avatar className="flex-shrink-0 hidden sm:inline-flex">
<AvatarFallback>
{view.viewerEmail?.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<VisitorAvatar viewerEmail={view.viewerEmail} />
<div className="min-w-0 flex-1">
<div className="focus:outline-none">
<p className="text-sm font-medium text-muted-foreground overflow-visible">
Expand Down
24 changes: 12 additions & 12 deletions components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";

import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";

const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
Expand All @@ -11,12 +11,12 @@ const Avatar = React.forwardRef<
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
className,
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
));
Avatar.displayName = AvatarPrimitive.Root.displayName;

const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
Expand All @@ -27,8 +27,8 @@ const AvatarImage = React.forwardRef<
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;

const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
Expand All @@ -38,11 +38,11 @@ const AvatarFallback = React.forwardRef<
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-gray-300 dark:bg-muted",
className
className,
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;

export { Avatar, AvatarImage, AvatarFallback }
export { Avatar, AvatarImage, AvatarFallback };
6 changes: 0 additions & 6 deletions components/view/toolbar.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { useEffect, useRef, useState } from "react";
import { Avatar, AvatarFallback } from "../ui/avatar";
import UserRound from "../shared/icons/user-round";
import { REACTIONS } from "@/lib/constants";
import GripVertical from "../shared/icons/grip-vertical";
import Draggable from "react-draggable";

function getKeyByValue(object: { [x: string]: any }, value: any) {
return Object.keys(object).find((key) => object[key] === value);
}

export default function Toolbar({
viewId,
pageNumber,
Expand Down
64 changes: 64 additions & 0 deletions components/visitors/visitor-avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { generateGravatarHash } from "@/lib/utils";

export const VisitorAvatar = ({
viewerEmail,
}: {
viewerEmail: string | null;
}) => {
// Convert email string to a simple hash
const hashString = (str: string) => {
let hash = 0;

for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0; // Convert to 32bit integer
}
return hash;
};

// Get the background color from the email number hash
const getColorFromHash = (hash: number): string => {
// An array of colors you want to choose from
const colors = [
"bg-gray-200/50",
"bg-gray-300/50",
"bg-gray-400/50",
"bg-gray-500/50",
"bg-gray-600/50",
];

// Use the hash to get an index for the colors array
const index = Math.abs(hash) % colors.length;
return colors[index];
};

if (!viewerEmail) {
return (
<Avatar className="flex-shrink-0 hidden sm:inline-flex">
<AvatarFallback className="bg-gray-200/50 dark:bg-gray-200/50">
AN
</AvatarFallback>
</Avatar>
);
}

return (
<Avatar className="flex-shrink-0 hidden sm:inline-flex">
<AvatarImage
src={`https://gravatar.com/avatar/${generateGravatarHash(
viewerEmail,
)}?s=80&d=404`}
/>

<AvatarFallback
className={`${getColorFromHash(
hashString(viewerEmail),
)} dark:${getColorFromHash(hashString(viewerEmail))}`}
>
{viewerEmail?.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
);
};
12 changes: 4 additions & 8 deletions components/visitors/visitors-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Gauge } from "@/components/ui/gauge";

import { useDocumentVisits } from "@/lib/swr/use-document";
import { durationFormat, timeAgo } from "@/lib/utils";
import { Skeleton } from "../ui/skeleton";
import ChevronDown from "../shared/icons/chevron-down";
import { Skeleton } from "@/components/ui/skeleton";
import ChevronDown from "@/components/shared/icons/chevron-down";
import VisitorChart from "./visitor-chart";
import { VisitorAvatar } from "./visitor-avatar";

export default function VisitorsTable({ numPages }: { numPages: number }) {
const { views } = useDocumentVisits();
Expand Down Expand Up @@ -48,11 +48,7 @@ export default function VisitorsTable({ numPages }: { numPages: number }) {
{/* Name */}
<TableCell className="">
<div className="flex items-center sm:space-x-3 overflow-visible">
<Avatar className="flex-shrink-0 hidden sm:inline-flex">
<AvatarFallback>
{view.viewerEmail?.slice(0, 2).toUpperCase()}
</AvatarFallback>
</Avatar>
<VisitorAvatar viewerEmail={view.viewerEmail} />
<div className="min-w-0 flex-1">
<div className="focus:outline-none">
<p className="text-sm font-medium text-gray-800 dark:text-gray-200 overflow-visible">
Expand Down
20 changes: 20 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { customAlphabet } from "nanoid";
import { ThreadMessage } from "openai/resources/beta/threads/messages/messages";
import { Message } from "ai";
import { upload } from "@vercel/blob/client";
import crypto from "crypto";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
Expand Down Expand Up @@ -383,3 +384,22 @@ export const uploadImage = async (file: File) => {

return newBlob.url;
};

/**
* Generates a Gravatar hash for the given email.
* @param {string} email - The email address.
* @returns {string} The Gravatar hash.
*/
export const generateGravatarHash = (email: string | null): string => {
if (!email) return "";
// 1. Trim leading and trailing whitespace from an email address
const trimmedEmail = email.trim();

// 2. Force all characters to lower-case
const lowerCaseEmail = trimmedEmail.toLowerCase();

// 3. Hash the final string with SHA256
const hash = crypto.createHash("sha256").update(lowerCaseEmail).digest("hex");

return hash;
};
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"framer-motion": "^10.16.14",
"fuse.js": "^6.6.2",
"js-cookie": "^3.0.5",
"lucide-react": "^0.292.0",
"lucide-react": "^0.316.0",
"ms": "^2.1.3",
"mupdf": "^0.1.1",
"nanoid": "^5.0.4",
Expand Down