-
Notifications
You must be signed in to change notification settings - Fork 122
feat(UI): add gpg-key setting #1397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 ( | ||||||||||||||||||||||||||||
| <div className="flex items-center justify-between py-4 border-b border-gray-200 last:border-b-0"> | ||||||||||||||||||||||||||||
| <div className="flex items-start"> | ||||||||||||||||||||||||||||
| <LockIcon className="w-6 h-6 text-gray-400" aria-hidden="true"/> | ||||||||||||||||||||||||||||
| <div className="ml-4"> | ||||||||||||||||||||||||||||
| <p className="text-base font-bold text-gray-900">{ keyData.fingerprint }</p> | ||||||||||||||||||||||||||||
| <p className="text-sm font-mono text-gray-500 mt-1">{ keyData.fingerprint }</p> | ||||||||||||||||||||||||||||
| <p className="text-xs text-gray-500 mt-2"> | ||||||||||||||||||||||||||||
| <HandleTime created_at={ Math.floor(new Date(keyData.created_at).getTime()) }/> | ||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||
| onClick={ () => deleteGPGKey( | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||
| key_id: keyData.key_id, | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| onSuccess: () => { | ||||||||||||||||||||||||||||
| queryClient.invalidateQueries({ queryKey: fetchGPGList.requestKey() }) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| className="px-4 py-1 text-sm font-semibold text-red-500 border border-gray-300 rounded-md hover:bg-red-500 hover:text-white transition-colors duration-200" | ||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||
| Delete | ||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| 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, | ||||||||||||||||||||||||||||
|
Comment on lines
+78
to
+81
|
||||||||||||||||||||||||||||
| postGPGKey( | |
| { | |
| data: { | |
| expires_days: 10, | |
| // Calculate the number of days between now and the selected expiration date | |
| const now = new Date(); | |
| const diffTime = expires_days.getTime() - now.getTime(); | |
| const diffDays = Math.max(1, Math.ceil(diffTime / (1000 * 60 * 60 * 24))); | |
| postGPGKey( | |
| { | |
| data: { | |
| expires_days: diffDays, |
Copilot
AI
Aug 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Large block of commented-out code should be removed rather than left in the codebase. If this functionality might be needed later, consider using feature flags or version control history.
| {/*<div className='mb-4'>*/ } | |
| {/* <TextField*/ } | |
| {/* autoFocus*/ } | |
| {/* label='title'*/ } | |
| {/* value={title}*/ } | |
| {/* onChange={setTitle}*/ } | |
| {/* />*/ } | |
| {/* {errors.title && <span className='text-red-500 text-xs'>{errors.title}</span>}*/ } | |
| {/*</div>*/ } |
Copilot
AI
Aug 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space before the question mark. Should be { isGPGLoading ? (
| { isGPGLoading? ( | |
| { isGPGLoading ? ( |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<DeleteApiGpgRemoveData, Error, { data: RemoveGpgRequest }>({ | ||
| mutationFn: ({ data }) => legacyApiClient.v1.deleteApiGpgRemove().request(data) | ||
| }) | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<GpgKey[]>(`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 } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<PostApiGpgAddData, Error, { data: NewGpgRequest }>({ | ||
| mutationFn: ({ data }) => legacyApiClient.v1.postApiGpgAdd().request(data) | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both lines display the same fingerprint value. The second line appears to be intended for a different piece of information (possibly key ID or a shortened version).