diff --git a/moon/apps/web/components/Setting/GPGKeys.tsx b/moon/apps/web/components/Setting/GPGKeys.tsx
new file mode 100644
index 000000000..c5e089036
--- /dev/null
+++ b/moon/apps/web/components/Setting/GPGKeys.tsx
@@ -0,0 +1,217 @@
+import React, { useState } from 'react';
+import { LoadingSpinner, LockIcon, Button, TextField, PlusIcon } from '@gitmono/ui'
+import * as Dialog from '@gitmono/ui/src/Dialog'
+import { DateAndTimePicker } from "@/components/DateAndTimePicker";
+import { useGetGPGList } from '@/hooks/useGetGPGList'
+import { usePostGPGKey } from '@/hooks/usePostGPGKey'
+import { useDeleteGPGKeyById } from '@/hooks/useDeleteGPGKeyById'
+import { legacyApiClient } from "@/utils/queryClient";
+import { useQueryClient } from "@tanstack/react-query";
+import HandleTime from "@/components/MrView/components/HandleTime";
+import { GpgKey } from "@gitmono/types";
+
+const GpgKeyItem = ({ keyData } : { keyData: GpgKey }) => {
+ const { mutate: deleteGPGKey } = useDeleteGPGKeyById()
+ const fetchGPGList = legacyApiClient.v1.getApiGpgList()
+ const queryClient = useQueryClient()
+
+ return (
+
+
+
+
+
{ keyData.fingerprint }
+
{ keyData.fingerprint }
+
+
+
+
+
+
+
+ )
+}
+
+interface NewGPGKeyDialogProps {
+ open: boolean;
+ setOpen: (open: boolean) => void;
+}
+
+const NewGPGKeyDialog = ({ open, setOpen }: NewGPGKeyDialogProps) => {
+ const { mutate: postGPGKey, isPending } = usePostGPGKey()
+ // const [title, setTitle] = useState('')
+ const [gpg_content, setGpg_content] = useState('')
+ const [errors, setErrors] = useState<{ title?: string; gpgKey?: string }>({})
+ const [expires_days, setExpiresDays] = useState(new Date())
+
+ const fetchGPGList = legacyApiClient.v1.getApiGpgList()
+ const queryClient = useQueryClient()
+
+ const handleSubmit = (e?: React.FormEvent | React.MouseEvent) => {
+ if (e) e.preventDefault()
+ const nextErrors: { title?: string; gpgKey?: string } = {}
+
+ // if (!title.trim()) nextErrors.title = 'Title is required'
+ if (!gpg_content.trim()) nextErrors.gpgKey = 'GPG key is required'
+ setErrors(nextErrors)
+ if (Object.keys(nextErrors).length > 0) return
+
+ // Normalize Windows-style line endings to Unix-style
+ const normalizedGpgContent = gpg_content.replace(/\r\n/g, '\n')
+
+ postGPGKey(
+ {
+ data: {
+ expires_days: 10,
+ gpg_content: normalizedGpgContent
+ }
+ },
+ {
+ onSuccess: () => {
+ setOpen(false)
+ // setTitle('')
+ setGpg_content('')
+ setErrors({})
+
+ queryClient.invalidateQueries({ queryKey: fetchGPGList.requestKey() })
+ }
+ }
+ )
+ }
+
+ return (
+
+
+ Add GPG key
+
+
+ {/**/ }
+ {/* */ }
+ {/* {errors.title && {errors.title}}*/ }
+ {/*
*/ }
+
+
+
+
+
+ { errors.gpgKey && { errors.gpgKey } }
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const GPGKeys = () => {
+ const { gpgKeys, isLoading: isGPGLoading } = useGetGPGList()
+ const [open, setOpen] = useState(false)
+
+ return (
+ <>
+
+
+ GPG keys
+ }
+ onClick={ () => setOpen(true) }
+ >
+ New GPG key
+
+
+
+
+ This is a list of GPG keys associated with your account. Remove any keys that you do not recognize.
+
+
+
+
+ Authentication keys
+
+ { isGPGLoading? (
+
+
+
+ ) : (
+
+ { gpgKeys?.map((key) => (
+
+ )) }
+
+ ) }
+
+
+
+ >
+ );
+};
+
+export default GPGKeys;
\ No newline at end of file
diff --git a/moon/apps/web/hooks/useDeleteGPGKeyById.ts b/moon/apps/web/hooks/useDeleteGPGKeyById.ts
new file mode 100644
index 000000000..354dd7d35
--- /dev/null
+++ b/moon/apps/web/hooks/useDeleteGPGKeyById.ts
@@ -0,0 +1,9 @@
+import { useMutation } from "@tanstack/react-query";
+import { DeleteApiGpgRemoveData, RemoveGpgRequest } from "@gitmono/types";
+import { legacyApiClient } from "@/utils/queryClient";
+
+export const useDeleteGPGKeyById = () => {
+ return useMutation({
+ mutationFn: ({ data }) => legacyApiClient.v1.deleteApiGpgRemove().request(data)
+ })
+};
\ No newline at end of file
diff --git a/moon/apps/web/hooks/useGetGPGList.ts b/moon/apps/web/hooks/useGetGPGList.ts
new file mode 100644
index 000000000..66af23fa3
--- /dev/null
+++ b/moon/apps/web/hooks/useGetGPGList.ts
@@ -0,0 +1,32 @@
+import { legacyApiClient } from "@/utils/queryClient";
+import { atomFamily } from "jotai/utils";
+import { atomWithWebStorage } from "@/utils/atomWithWebStorage";
+import { GpgKey } from "@gitmono/types";
+import { useAtom } from "jotai";
+import { useQuery } from "@tanstack/react-query";
+import { useEffect } from "react";
+
+const fetchGPGList = legacyApiClient.v1.getApiGpgList()
+const getGPGListAtom = atomFamily(() =>
+ atomWithWebStorage(`gpg-key`, [])
+)
+
+export const useGetGPGList = () => {
+ const [gpgKeys, setGpgKeys] = useAtom(getGPGListAtom('gpg-key'))
+ const { data, isLoading } = useQuery({
+ queryKey: fetchGPGList.requestKey(),
+ queryFn: async () => {
+ const response = await fetchGPGList.request()
+
+ return response.data
+ }
+ });
+
+ useEffect(() => {
+ if(data) {
+ setGpgKeys(data)
+ }
+ }, [data, setGpgKeys]);
+
+ return { gpgKeys, isLoading }
+};
\ No newline at end of file
diff --git a/moon/apps/web/hooks/usePostGPGKey.ts b/moon/apps/web/hooks/usePostGPGKey.ts
new file mode 100644
index 000000000..a7066b84a
--- /dev/null
+++ b/moon/apps/web/hooks/usePostGPGKey.ts
@@ -0,0 +1,9 @@
+import { useMutation } from "@tanstack/react-query";
+import { NewGpgRequest, PostApiGpgAddData } from "@gitmono/types";
+import { legacyApiClient } from "@/utils/queryClient";
+
+export const usePostGPGKey = () => {
+ return useMutation({
+ mutationFn: ({ data }) => legacyApiClient.v1.postApiGpgAdd().request(data)
+ })
+}
\ No newline at end of file
diff --git a/moon/apps/web/pages/me/settings/index.tsx b/moon/apps/web/pages/me/settings/index.tsx
index 191e60f0d..933f6d216 100644
--- a/moon/apps/web/pages/me/settings/index.tsx
+++ b/moon/apps/web/pages/me/settings/index.tsx
@@ -17,6 +17,7 @@ import { PersonalCallLinks } from '@/components/UserSettings/PersonalCallLinks';
import { SlackNotificationSettings } from '@/components/UserSettings/SlackNotificationSettings';
import { Timezone } from '@/components/UserSettings/Timezone'
import { PageWithProviders } from '@/utils/types';
+import GPGKeys from "@/components/Setting/GPGKeys";
const UserSettingsPage: PageWithProviders = () => {
useEffect(() => {
@@ -43,6 +44,7 @@ const UserSettingsPage: PageWithProviders = () => {
+