Skip to content

Commit

Permalink
Allow searching accountds based on more than one signature (#238)
Browse files Browse the repository at this point in the history
* Allow searching accountds based on more than one signature

* tidy

* refactor

* tidy

* use json encoding

* tidy
  • Loading branch information
matthieusieben authored Nov 19, 2024
1 parent 4997934 commit 97c4de6
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 81 deletions.
42 changes: 24 additions & 18 deletions app/repositories/page-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,32 +31,27 @@ const getRepos =
excludeRepo?: boolean
},
options: { signal?: AbortSignal } = {},
) => {
): Promise<{
repos: ToolsOzoneModerationDefs.RepoView[]
cursor?: string
}> => {
const limit = 25

if (!isEmailSearch(q) && !isSignatureSearch(q)) {
const { data } = await labelerAgent.tools.ozone.moderation.searchRepos(
{
q,
limit,
cursor: pageParam,
},
options,
)

return data
}

let data: ComAtprotoAdminSearchAccounts.OutputSchema
if (isSignatureSearch(q)) {
const value = q.replace('sig:', '').trim()
const rawValue = q.slice(4)
const values =
rawValue.startsWith('["') && q.endsWith('"]')
? // JSON array of strings
JSON.parse(rawValue)
: [rawValue.trim()] // slice 'sig:' prefix
const res = await labelerAgent.tools.ozone.signature.searchAccounts({
values: [value],
values,
cursor: pageParam,
})
data = res.data
} else {
const email = q.replace('email:', '').trim()
} else if (isEmailSearch(q)) {
const email = q.slice(6).trim() // slice 'email:' prefix
const res = await labelerAgent.com.atproto.admin.searchAccounts(
{
email,
Expand All @@ -66,6 +61,17 @@ const getRepos =
options,
)
data = res.data
} else {
const res = await labelerAgent.tools.ozone.moderation.searchRepos(
{
q,
limit,
cursor: pageParam,
},
options,
)

return res.data
}

if (!data.accounts.length) {
Expand Down
9 changes: 7 additions & 2 deletions components/common/CopyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { ClipboardIcon } from '@heroicons/react/20/solid'
import { ReactNode } from 'react'
import { toast } from 'react-toastify'

export const CopyButton = ({
text,
label,
labelText = typeof label === 'string' && label ? `${label} ` : '',
...rest
}: { text: string; label?: string } & JSX.IntrinsicElements['button']) => {
const labelText = label ? `${label} ` : ''
}: {
text: string
label?: ReactNode
labelText?: string
} & JSX.IntrinsicElements['button']) => {
const handleCopy = (e) => {
e.preventDefault()
toast.promise(navigator.clipboard.writeText(text), {
Expand Down
5 changes: 4 additions & 1 deletion components/common/DataField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { ReactNode } from 'react'
import { CopyButton } from './CopyButton'

export type DataFieldProps = {
label: string
label: ReactNode
labelText?: string
value?: string
showCopyButton?: boolean
children?: ReactNode
Expand All @@ -12,6 +13,7 @@ export type DataFieldProps = {

export const DataField = ({
label,
labelText,
value,
children,
showCopyButton,
Expand All @@ -30,6 +32,7 @@ export const DataField = ({
text={value}
className="ml-1"
label={label}
labelText={labelText}
title={`Copy ${label} to clipboard`}
/>
)}
Expand Down
16 changes: 11 additions & 5 deletions components/common/forms/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentProps, forwardRef } from 'react'
import { ComponentProps, forwardRef, ReactNode, useState } from 'react'
import { CopyButton } from '../CopyButton'
import { classNames } from '@/lib/util'

Expand Down Expand Up @@ -39,13 +39,13 @@ export const Textarea = forwardRef<
)
})

type LabelProps = { label: string | JSX.Element; required?: boolean }
type LabelProps = { label: string | ReactNode; required?: boolean }
type CopyProps = { copyButton?: { text: string; label?: string } }

export function FormLabel(
props: ComponentProps<'label'> &
LabelProps &
CopyProps & { extraLabel?: JSX.Element },
CopyProps & { extraLabel?: ReactNode },
) {
const {
label,
Expand Down Expand Up @@ -85,9 +85,14 @@ type CheckboxProps = LabelProps &

export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
function CheckboxElement(
{ label, required, className, inputClassName, ...rest }: CheckboxProps,
{ label, required, className, inputClassName, id, ...rest }: CheckboxProps,
ref,
) {
const [fallbackId] = useState(
// Make sure this rune only once per component instance
() => `__my_checkbox-${Math.random().toString(36).substring(7)}`,
)

return (
<div className={className}>
<input
Expand All @@ -97,10 +102,11 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
'h-4 w-4 rounded border-gray-300 text-indigo-600 dark:text-teal-500 focus:ring-indigo-600 dark:focus:ring-teal-500 mr-1',
inputClassName,
)}
id={id ?? fallbackId}
{...rest}
/>
<label
htmlFor={rest.name}
htmlFor={id ?? fallbackId}
className="ml-1 text-sm leading-6 font-medium text-gray-900 dark:text-gray-200"
>
{label}
Expand Down
106 changes: 106 additions & 0 deletions components/common/modals/checkboxes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Checkbox } from '@/common/forms'
import { ConfirmationModal } from '@/common/modals/confirmation'
import {
ComponentProps,
ReactNode,
useCallback,
useEffect,
useRef,
useState,
} from 'react'

export type CheckboxesModalProps<T> = {
title: string
items: T[]
itemCmp?: (a: T, b: T) => boolean
itemLabel?: (item: T) => string | ReactNode
label?: string
value?: T[]
required?: boolean
confirmButtonText?: string
onChange?: (value: T[]) => void
onConfirm?: (value: T[]) => void
} & ComponentProps<'button'>

export function CheckboxesModal<T>({
title,
value,
required,
onChange,
onConfirm,
items,
itemCmp = (a, b) => a === b,
itemLabel,
confirmButtonText,

// button props
children,
onClick,
...buttonProps
}: CheckboxesModalProps<T>) {
const dialogRef = useRef<HTMLDivElement>(null)
const [isOpen, setIsOpen] = useState(false)
const [internal, setInternal] = useState<T[]>(value ?? [])

const reset = useCallback(() => {
setInternal(value ?? [])
}, [value])

const updateValue = (newValue: T[]) => {
setInternal(newValue)
onChange?.(newValue)
}

const updateOpen = (isOpen: boolean) => {
reset()
setIsOpen(isOpen)
}

useEffect(() => {
reset()
}, [reset])

return (
<button
{...buttonProps}
onClick={(event) => {
onClick?.(event)
if (
!event.defaultPrevented &&
!dialogRef.current?.contains(event.target as Node)
) {
updateOpen(true)
}
}}
>
{children}
<ConfirmationModal
ref={dialogRef}
isOpen={isOpen}
title={title}
confirmButtonText={confirmButtonText}
confirmButtonDisabled={!!required && !internal.length}
onConfirm={() => {
if (internal.length || !required) {
onConfirm?.(internal)
updateOpen(false)
}
}}
setIsOpen={updateOpen}
>
{items?.map((item, i) => (
<Checkbox
key={i}
className="mt-3 flex items-center"
label={itemLabel?.(item) ?? `Item ${i + 1}`}
checked={internal.some((other) => itemCmp(other, item))}
onChange={(e) => {
const filtered = internal.filter((other) => !itemCmp(other, item))
updateValue(e.target.checked ? [...filtered, item] : filtered)
}}
/>
))}
</ConfirmationModal>
</button>
)
}
37 changes: 21 additions & 16 deletions components/common/modals/confirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment } from 'react'

import { ActionButton } from '@/common/buttons'
import { ForwardedRef, forwardRef, Fragment } from 'react'
import { Alert } from '@/common/Alert'
import { ActionButton } from '@/common/buttons'

export const ConfirmationModal = ({
children,
isOpen = false,
description,
title,
error,
setIsOpen,
onConfirm,
confirmButtonDisabled = false,
confirmButtonText = 'Confirm',
}: {
export type ConfirmationModalProps = {
isOpen: boolean
description?: JSX.Element
title: string
Expand All @@ -25,10 +14,26 @@ export const ConfirmationModal = ({
setIsOpen: (isOpen: boolean) => void
confirmButtonDisabled?: boolean
confirmButtonText?: string
}) => {
}

export const ConfirmationModal = forwardRef(function ConfirmationModal(
{
children,
isOpen = false,
description,
title,
error,
setIsOpen,
onConfirm,
confirmButtonDisabled = false,
confirmButtonText = 'Confirm',
}: ConfirmationModalProps,
ref: ForwardedRef<HTMLDivElement>,
) {
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog
ref={ref}
as="div"
className="relative z-10"
onClose={() => setIsOpen(false)}
Expand Down Expand Up @@ -103,4 +108,4 @@ export const ConfirmationModal = ({
</Dialog>
</Transition>
)
}
})
Loading

0 comments on commit 97c4de6

Please sign in to comment.