import { useState } from "react";
import { 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 { 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 { RedactedOnePasswordConfig } from "../sdk/types";
// ---------------------------------------------------------------------------
// Vault picker
// ---------------------------------------------------------------------------
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;
vaultId: string;
onVaultSelect: (id: string, name: string) => void;
}) {
const account = props.accountName.trim();
const vaultsResult = useAtomValue(onepasswordVaultsAtom(props.authKind, account));
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 defaultVault = v[0];
if (
defaultVault &&
(!props.vaultId || (v.length === 1 && props.vaultId !== defaultVault.id))
) {
queueMicrotask(() => props.onVaultSelect(defaultVault.id, defaultVault.name));
}
return { vaults: [...v], isLoading: false, error: null };
},
},
);
if (!account) {
return (
Enter account details to load vaults.
);
}
const singleVault = vaults.length === 1 ? vaults[0] : null;
return (
{singleVault ? (
{singleVault.name}
) : (
)}
{error && (
)}
);
}
// ---------------------------------------------------------------------------
// Config dialog
// ---------------------------------------------------------------------------
function ConfigDialog(props: {
open: boolean;
onOpenChange: (v: boolean) => void;
initial?: {
authKind: string;
accountName: string;
vaultId: string;
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 [vaultId, setVaultId] = useState(props.initial?.vaultId ?? "");
const [vaultName, setVaultName] = 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");
setVaultId("");
setVaultName("");
}
setError(null);
setSaving(false);
};
const handleSave = async () => {
if (!accountName.trim() || !vaultId.trim()) 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: {
auth,
vaultId: vaultId.trim(),
name: vaultName.trim() || "1Password",
},
reactivityKeys: onepasswordWriteKeys,
});
if (Exit.isFailure(exit)) {
setError("Failed to save configuration");
setSaving(false);
return;
}
props.onOpenChange(false);
reset();
};
return (
);
}
// ---------------------------------------------------------------------------
// Settings card
// ---------------------------------------------------------------------------
export default function OnePasswordSettings() {
const [configOpen, setConfigOpen] = useState(false);
const configResult = useAtomValue(onepasswordConfigAtom);
const doRemove = useAtomSet(removeOnePasswordConfig, { mode: "promiseExit" });
const handleRemove = async () => {
await doRemove({ reactivityKeys: onepasswordWriteKeys });
};
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,
},
);
return (
<>
{isLoading ? (
Loading…
) : isError ? (
Failed to load configuration
) : config ? (
Auth
{config.auth.kind === "desktop-app" ? config.auth.accountName : "service-account"}
Vault
{config.name}
) : (
Resolve secrets from your 1Password vault.
)}
{config ? (
<>
>
) : (
!isLoading &&
!isError && (
)
)}
{configOpen && (
)}
>
);
}