import { useState } from "react";
import { useAtomSet, useAtomValue } from "@effect-atom/atom-react";
import { Result } from "@effect-atom/atom-react";
import { ReactivityKey } from "@executor/react/api/reactivity-keys";
import { useScope } from "@executor/react/api/scope-context";
import { Button } from "@executor/react/components/button";
import { Input } from "@executor/react/components/input";
import { Label } from "@executor/react/components/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@executor/react/components/select";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
DialogClose,
} from "@executor/react/components/dialog";
import {
CardStackEntry,
CardStackEntryActions,
CardStackEntryContent,
CardStackEntryDescription,
} from "@executor/react/components/card-stack";
import {
onepasswordConfigAtom,
onepasswordVaultsAtom,
configureOnePassword,
removeOnePasswordConfig,
} from "./atoms";
import type { OnePasswordConfig } from "../sdk/types";
// ---------------------------------------------------------------------------
// Vault picker
// ---------------------------------------------------------------------------
function VaultPicker(props: {
authKind: "desktop-app" | "service-account";
accountName: string;
vaultId: string;
onVaultSelect: (id: string, name: string) => void;
}) {
const account = props.accountName.trim();
const scopeId = useScope();
const vaultsResult = useAtomValue(onepasswordVaultsAtom(props.authKind, account, scopeId));
const { vaults, isLoading, error } = Result.matchWithError(
vaultsResult as Result.Result<{ vaults: ReadonlyArray<{ id: string; name: string }> }, Error>,
{
onInitial: () => ({
vaults: [] as { id: string; name: string }[],
isLoading: true,
error: null,
}),
onError: (error) => ({
vaults: [] as { id: string; name: string }[],
isLoading: false,
error: error.message,
}),
onDefect: (defect) => ({
vaults: [] as { id: string; name: string }[],
isLoading: false,
error: defect instanceof Error ? defect.message : "Failed to list vaults",
}),
onSuccess: ({ value }) => {
const v = value.vaults;
if (v.length > 0 && !props.vaultId) {
queueMicrotask(() => props.onVaultSelect(v[0].id, v[0].name));
}
return { vaults: [...v], isLoading: false, error: null };
},
},
);
if (!account) {
return (
Enter account details to load vaults.
);
}
return (
{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 scopeId = useScope();
const doConfigure = useAtomSet(configureOnePassword, { mode: "promise" });
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);
try {
const auth =
authKind === "desktop-app"
? { kind: "desktop-app" as const, accountName: accountName.trim() }
: { kind: "service-account" as const, tokenSecretId: accountName.trim() };
await doConfigure({
path: { scopeId },
payload: { auth, vaultId: vaultId.trim(), name: vaultName.trim() || "1Password" },
reactivityKeys: [ReactivityKey.secrets],
});
props.onOpenChange(false);
reset();
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to save configuration");
setSaving(false);
}
};
return (
);
}
// ---------------------------------------------------------------------------
// Settings card
// ---------------------------------------------------------------------------
export default function OnePasswordSettings() {
const [configOpen, setConfigOpen] = useState(false);
const scopeId = useScope();
const configResult = useAtomValue(onepasswordConfigAtom(scopeId));
const doRemove = useAtomSet(removeOnePasswordConfig, { mode: "promise" });
const handleRemove = async () => {
try {
await doRemove({ path: { scopeId }, reactivityKeys: [ReactivityKey.secrets] });
} catch {
/* TODO: toast */
}
};
const config: OnePasswordConfig | null = Result.match(
configResult as Result.Result,
{ onInitial: () => null, onFailure: () => null, onSuccess: ({ value }) => value },
);
const isLoading = Result.match(configResult as Result.Result, {
onInitial: () => true,
onFailure: () => false,
onSuccess: () => false,
});
const isError = Result.match(configResult as Result.Result, {
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}
{config.vaultId}
) : (
Resolve secrets from your 1Password vault.
)}
{config ? (
<>
>
) : (
!isLoading &&
!isError && (
)
)}
{configOpen && (
)}
>
);
}