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 ( +
+
+
+ +
+ ) +} + +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

+ +
+ +

+ 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 = () => { +