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 { ReactivityKey } from "@executor-js/react/api/reactivity-keys";
import { useScope } from "@executor-js/react/api/scope-context";
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,
} 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 } = 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: () => ({
vaults: [] as { id: string; name: string }[],
isLoading: false,
error: "Failed to list vaults",
}),
onDefect: () => ({
vaults: [] as { id: string; name: string }[],
isLoading: false,
error: "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: "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, tokenSecretId: accountName.trim() };
const exit = await doConfigure({
params: { scopeId },
payload: { auth, vaultId: vaultId.trim(), name: vaultName.trim() || "1Password" },
reactivityKeys: [ReactivityKey.secrets],
});
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 scopeId = useScope();
const configResult = useAtomValue(onepasswordConfigAtom(scopeId));
const doRemove = useAtomSet(removeOnePasswordConfig, { mode: "promiseExit" });
const handleRemove = async () => {
await doRemove({ params: { scopeId }, reactivityKeys: [ReactivityKey.secrets] });
};
const config: OnePasswordConfig | 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}
{config.vaultId}
) : (
Resolve secrets from your 1Password vault.
)}
{config ? (
<>
>
) : (
!isLoading &&
!isError && (
)
)}
{configOpen && (
)}
>
);
}