committed by
GitHub
parent
a07025422e
commit
6b9696e715
@@ -12,6 +12,14 @@ const config = {
|
|||||||
esmExternals: "loose",
|
esmExternals: "loose",
|
||||||
serverComponentsExternalPackages: ["bullmq"],
|
serverComponentsExternalPackages: ["bullmq"],
|
||||||
},
|
},
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "www.gravatar.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
@@ -1,15 +1,5 @@
|
|||||||
"use client";
|
"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 { Button } from "@unsend/ui/src/button";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -18,7 +8,19 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "@unsend/ui/src/select";
|
} from "@unsend/ui/src/select";
|
||||||
import Spinner from "@unsend/ui/src/spinner";
|
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 { 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 DeleteContact from "./delete-contact";
|
||||||
import EditContact from "./edit-contact";
|
import EditContact from "./edit-contact";
|
||||||
|
|
||||||
@@ -89,7 +91,21 @@ export default function ContactList({
|
|||||||
) : contactsQuery.data?.contacts.length ? (
|
) : contactsQuery.data?.contacts.length ? (
|
||||||
contactsQuery.data?.contacts.map((contact) => (
|
contactsQuery.data?.contacts.map((contact) => (
|
||||||
<TableRow key={contact.id} className="">
|
<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>
|
<TableCell>
|
||||||
<div
|
<div
|
||||||
className={`text-center w-[130px] rounded capitalize py-1 text-xs ${
|
className={`text-center w-[130px] rounded capitalize py-1 text-xs ${
|
||||||
|
62
apps/web/src/utils/gravatar-utils.ts
Normal file
62
apps/web/src/utils/gravatar-utils.ts
Normal 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;
|
||||||
|
}
|
Reference in New Issue
Block a user