From 1c5fe8a2836d82ab21a9e1c68c63c527d60282da Mon Sep 17 00:00:00 2001 From: JiPai Date: Mon, 18 Nov 2024 14:09:42 +0800 Subject: [PATCH 1/2] feat(Profile): support use Gravatar as avatar --- .../settings/profile/profile-form.tsx | 19 +++++++++++++++++-- apps/dokploy/lib/utils.ts | 8 ++++++++ .../pages/dashboard/settings/appearance.tsx | 1 - 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx index e90eb5e5..1d4daa53 100644 --- a/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx +++ b/apps/dokploy/components/dashboard/settings/profile/profile-form.tsx @@ -16,10 +16,11 @@ import { } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { generateSHA256Hash } from "@/lib/utils"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { useTranslation } from "next-i18next"; -import { useEffect } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -53,6 +54,14 @@ export const ProfileForm = () => { const { data, refetch } = api.auth.get.useQuery(); const { mutateAsync, isLoading } = api.auth.update.useMutation(); const { t } = useTranslation("settings"); + const [gravatarHash, setGravatarHash] = useState(null); + + const availableAvatars = useMemo(() => { + if (gravatarHash === null) return randomImages; + return randomImages.concat([ + `https://www.gravatar.com/avatar/${gravatarHash}`, + ]); + }, [gravatarHash]); const form = useForm({ defaultValues: { @@ -70,6 +79,12 @@ export const ProfileForm = () => { password: "", image: data?.image || "", }); + + if (data.email) { + generateSHA256Hash(data.email).then((hash) => { + setGravatarHash(hash); + }); + } } form.reset(); }, [form, form.reset, data]); @@ -154,7 +169,7 @@ export const ProfileForm = () => { value={field.value} className="flex flex-row flex-wrap gap-2 max-xl:justify-center" > - {randomImages.map((image) => ( + {availableAvatars.map((image) => ( diff --git a/apps/dokploy/lib/utils.ts b/apps/dokploy/lib/utils.ts index ac680b30..c83f5e22 100644 --- a/apps/dokploy/lib/utils.ts +++ b/apps/dokploy/lib/utils.ts @@ -4,3 +4,11 @@ import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } + +export async function generateSHA256Hash(text: string) { + const encoder = new TextEncoder(); + const data = encoder.encode(text); + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); +} diff --git a/apps/dokploy/pages/dashboard/settings/appearance.tsx b/apps/dokploy/pages/dashboard/settings/appearance.tsx index 209d938c..f7f49746 100644 --- a/apps/dokploy/pages/dashboard/settings/appearance.tsx +++ b/apps/dokploy/pages/dashboard/settings/appearance.tsx @@ -8,7 +8,6 @@ import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; import React, { type ReactElement } from "react"; import superjson from "superjson"; -import nextI18NextConfig from "../../../next-i18next.config.cjs"; const Page = () => { return ( From 6fc1ce2fbc7e47fd6ccb01c37e430c215f8c5986 Mon Sep 17 00:00:00 2001 From: JiPai Date: Mon, 18 Nov 2024 22:25:56 +0800 Subject: [PATCH 2/2] chore(README): fix broken video thumbnail --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7886f42a..ff25f7d0 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com). ## Video Tutorial - Watch the video + Watch the video