feat: contact gravatar (#89) (#90)

This commit is contained in:
Shree Krishna Lamichhane
2025-01-25 18:45:12 +05:45
committed by GitHub
parent a07025422e
commit 6b9696e715
3 changed files with 97 additions and 11 deletions

View File

@@ -12,6 +12,14 @@ const config = {
esmExternals: "loose",
serverComponentsExternalPackages: ["bullmq"],
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "www.gravatar.com",
},
],
},
};
export default config;

View File

@@ -1,15 +1,5 @@
"use client";
import {
Table,
TableHeader,
TableRow,
TableHead,
TableBody,
TableCell,
} from "@unsend/ui/src/table";
import { api } from "~/trpc/react";
import { useUrlState } from "~/hooks/useUrlState";
import { Button } from "@unsend/ui/src/button";
import {
Select,
@@ -18,7 +8,19 @@ import {
SelectTrigger,
} from "@unsend/ui/src/select";
import Spinner from "@unsend/ui/src/spinner";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@unsend/ui/src/table";
import { formatDistanceToNow } from "date-fns";
import Image from "next/image";
import { useUrlState } from "~/hooks/useUrlState";
import { api } from "~/trpc/react";
import { getGravatarUrl } from "~/utils/gravatar-utils";
import DeleteContact from "./delete-contact";
import EditContact from "./edit-contact";
@@ -89,7 +91,21 @@ export default function ContactList({
) : contactsQuery.data?.contacts.length ? (
contactsQuery.data?.contacts.map((contact) => (
<TableRow key={contact.id} className="">
<TableCell className="font-medium">{contact.email}</TableCell>
<TableCell className="font-medium">
<div className="flex items-center gap-2">
<Image
src={getGravatarUrl(contact.email, {
size: 35,
defaultImage: "identicon",
})}
alt={contact.email + "'s gravatar"}
width={35}
height={35}
className="rounded-full"
/>
{contact.email}
</div>
</TableCell>
<TableCell>
<div
className={`text-center w-[130px] rounded capitalize py-1 text-xs ${

View File

@@ -0,0 +1,62 @@
import crypto from "crypto";
/**
* Possible Gravatar rating values: 'g' (general), 'pg' (parental guidance),
* 'r' (restricted), or 'x' (explicit).
*/
export type GravatarRating = "g" | "pg" | "r" | "x";
/**
* Common default image options in Gravatar.
* Can also be a URL (string) for a custom default image.
*/
export type GravatarDefaultImage =
| "404"
| "mp"
| "identicon"
| "monsterid"
| "wavatar"
| "retro"
| "robohash"
| "blank";
export interface GravatarOptions {
size?: number; // specify the size (in pixels)
defaultImage?: GravatarDefaultImage; // default image
rating?: GravatarRating; // image rating
}
export function getGravatarUrl(
email: string,
options: GravatarOptions = {
size: 80,
defaultImage: "identicon",
rating: "g",
},
) {
const trimmedEmail = email.trim().toLowerCase();
const hash = crypto.createHash("sha256").update(trimmedEmail).digest("hex");
// Base Gravatar URL
const baseUrl = "https://www.gravatar.com/avatar/";
// Use URLSearchParams to build query string
const queryParams = new URLSearchParams();
if (options.size) {
queryParams.set("s", options.size.toString());
}
if (options.defaultImage) {
queryParams.set("d", options.defaultImage);
}
if (options.rating) {
queryParams.set("r", options.rating);
}
const queryString = queryParams.toString();
const finalUrl = queryString
? `${baseUrl}${hash}?${queryString}`
: `${baseUrl}${hash}`;
return finalUrl;
}