import { useEffect, useRef, useState } from "react"; import { useAtomRefresh, useAtomSet, useAtomValue } from "@effect/atom-react"; import * as Exit from "effect/Exit"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { Button } from "@executor-js/react/components/button"; import { Checkbox } from "@executor-js/react/components/checkbox"; import { Input } from "@executor-js/react/components/input"; import { Label } from "@executor-js/react/components/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@executor-js/react/components/select"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, } from "@executor-js/react/components/dialog"; import { CardStackEntry, CardStackEntryActions, CardStackEntryContent, CardStackEntryDescription, } from "@executor-js/react/components/card-stack"; import { onepasswordConfigAtom, onepasswordVaultsAtom, configureOnePassword, removeOnePasswordConfig, onepasswordWriteKeys, } from "./atoms"; import type { RedactedOnePasswordAccount, RedactedOnePasswordConfig, Vault } from "../sdk/types"; // --------------------------------------------------------------------------- // Vault picker — multi-select // --------------------------------------------------------------------------- const VAULT_LIST_ERROR_FALLBACK = "Failed to list vaults"; const formatVaultListError = (error: Error): string => { // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: OnePasswordError carries a typed `message` const message = error.message.trim(); return message ? `${VAULT_LIST_ERROR_FALLBACK}: ${message}` : VAULT_LIST_ERROR_FALLBACK; }; function VaultPicker(props: { authKind: "desktop-app" | "service-account"; accountName: string; selected: ReadonlyArray; onSelectedChange: (vaults: ReadonlyArray) => void; }) { const account = props.accountName.trim(); const vaultsAtom = onepasswordVaultsAtom(props.authKind, account); const vaultsResult = useAtomValue(vaultsAtom); const refreshVaults = useAtomRefresh(vaultsAtom); // Stale-while-revalidate: with a retained value the vault list renders // instantly and one background refresh per atom key picks up changes // (refreshing keeps the previous value, so nothing flashes). A cold key is // already fetching — refreshing it would only restart the request. The ref // carries the latest cached-ness into the effect without re-running it. const isCachedRef = useRef(false); isCachedRef.current = AsyncResult.isSuccess(vaultsResult); useEffect(() => { if (isCachedRef.current) refreshVaults(); }, [refreshVaults]); const { vaults, isLoading, error } = AsyncResult.matchWithError( vaultsResult as AsyncResult.AsyncResult< { vaults: ReadonlyArray<{ id: string; name: string }> }, Error >, { onInitial: () => ({ vaults: [] as { id: string; name: string }[], isLoading: true, error: null, }), onError: (queryError) => ({ vaults: [] as { id: string; name: string }[], isLoading: false, error: formatVaultListError(queryError), }), onDefect: () => ({ vaults: [] as { id: string; name: string }[], isLoading: false, error: VAULT_LIST_ERROR_FALLBACK, }), onSuccess: ({ value }) => { const v = value.vaults; const onlyVault = v.length === 1 ? v[0] : undefined; if (onlyVault && props.selected.length === 0) { queueMicrotask(() => props.onSelectedChange([onlyVault])); } return { vaults: [...v], isLoading: false, error: null }; }, }, ); if (!account) { return (

Enter account details to load vaults.

); } // Selected vaults missing from the loaded list (renamed, revoked, or the // list failed to load while editing) stay visible so they can be unchecked. const loadedIds = new Set(vaults.map((v) => v.id)); const stale = props.selected.filter((v) => !loadedIds.has(v.id)); const rows = [...vaults, ...stale]; const toggle = (vault: Vault, checked: boolean) => { if (checked) { if (!props.selected.some((v) => v.id === vault.id)) { props.onSelectedChange([...props.selected, vault]); } return; } props.onSelectedChange(props.selected.filter((v) => v.id !== vault.id)); }; return (
{isLoading ? (

Loading vaults…

) : rows.length === 0 ? (

No vaults found.

) : (
{rows.map((vault) => { const checked = props.selected.some((v) => v.id === vault.id); return ( ); })}
)} {error && (

{error}

)}
); } // --------------------------------------------------------------------------- // Account dialog — add a new account, or edit one by id // --------------------------------------------------------------------------- function AccountDialog(props: { open: boolean; onOpenChange: (v: boolean) => void; initial?: { id: string; authKind: string; accountName: string; vaults: ReadonlyArray; name: string; }; }) { const isEdit = !!props.initial; const [authKind, setAuthKind] = useState<"desktop-app" | "service-account">( (props.initial?.authKind as "desktop-app" | "service-account") ?? "desktop-app", ); const [accountName, setAccountName] = useState(props.initial?.accountName ?? "my.1password.com"); const [selectedVaults, setSelectedVaults] = useState>( props.initial?.vaults ?? [], ); const [displayName, setDisplayName] = useState(props.initial?.name ?? ""); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const doConfigure = useAtomSet(configureOnePassword, { mode: "promiseExit" }); const reset = () => { if (!isEdit) { setAuthKind("desktop-app"); setAccountName("my.1password.com"); setSelectedVaults([]); setDisplayName(""); } setError(null); setSaving(false); }; const handleSave = async () => { const [firstVault, ...restVaults] = selectedVaults; if (!accountName.trim() || firstVault === undefined) return; setSaving(true); setError(null); const auth = authKind === "desktop-app" ? { kind: "desktop-app" as const, accountName: accountName.trim() } : { kind: "service-account" as const, token: accountName.trim() }; const exit = await doConfigure({ payload: { ...(props.initial ? { id: props.initial.id } : {}), auth, vaults: [firstVault, ...restVaults], name: displayName.trim() || "1Password", }, reactivityKeys: onepasswordWriteKeys, }); if (Exit.isFailure(exit)) { setError("Failed to save configuration"); setSaving(false); return; } props.onOpenChange(false); reset(); }; return ( { if (!v) reset(); props.onOpenChange(v); }} > {isEdit ? "Edit 1Password account" : "Add 1Password account"} Link one or more vaults to resolve secrets via the 1Password desktop app or a service account. Add more accounts to keep work and personal credentials separate.
{/* Auth method */}
{/* Account / token */}
setAccountName((e.target as HTMLInputElement).value)} className="font-mono text-[13px] h-9" />

{authKind === "desktop-app" ? "Requires the 1Password desktop app with biometric unlock." : "The token is stored in this provider's owner-scoped config and never surfaced again."}

{/* Vaults */}
{/* Display name */}
setDisplayName((e.target as HTMLInputElement).value)} className="text-[13px] h-9" />
{error && (

{error}

)}
); } // --------------------------------------------------------------------------- // Settings entries — one card per account, plus the add action // --------------------------------------------------------------------------- function AccountEntry(props: { account: RedactedOnePasswordAccount; onEdit: () => void }) { const doRemove = useAtomSet(removeOnePasswordConfig, { mode: "promiseExit" }); const [removing, setRemoving] = useState(false); const handleRemove = async () => { setRemoving(true); await doRemove({ query: { accountId: props.account.id }, reactivityKeys: onepasswordWriteKeys, }); setRemoving(false); }; return (
Name {props.account.name} Auth {props.account.auth.kind === "desktop-app" ? props.account.auth.accountName : "service-account"} {props.account.vaults.length === 1 ? "Vault" : "Vaults"} {props.account.vaults.map((vault) => vault.name).join(", ")}
); } export default function OnePasswordSettings() { // null = closed; "new" = add flow; otherwise the account being edited. const [dialogTarget, setDialogTarget] = useState(null); const configResult = useAtomValue(onepasswordConfigAtom); const config: RedactedOnePasswordConfig | null = AsyncResult.match( configResult as AsyncResult.AsyncResult, { onInitial: () => null, onFailure: () => null, onSuccess: ({ value }) => value, }, ); const isLoading = AsyncResult.match( configResult as AsyncResult.AsyncResult, { onInitial: () => true, onFailure: () => false, onSuccess: () => false, }, ); const isError = AsyncResult.match( configResult as AsyncResult.AsyncResult, { onInitial: () => false, onFailure: () => true, onSuccess: () => false, }, ); const accounts = config?.accounts ?? []; return ( <> {isLoading || isError || accounts.length === 0 ? ( {isLoading ? ( Loading… ) : isError ? ( Failed to load configuration ) : ( Resolve secrets from your 1Password vaults. )} {!isLoading && !isError && ( )} ) : ( <> {accounts.map((account) => ( setDialogTarget(account)} /> ))} )} {dialogTarget !== null && ( { if (!v) setDialogTarget(null); }} initial={ dialogTarget === "new" ? undefined : { id: dialogTarget.id, authKind: dialogTarget.auth.kind, // Service-account tokens are never surfaced (redacted); the // user re-enters the token when editing that auth method. accountName: dialogTarget.auth.kind === "desktop-app" ? dialogTarget.auth.accountName : "", vaults: dialogTarget.vaults, name: dialogTarget.name, } } /> )} ); }