A bunch of fixes

This commit is contained in:
Gregory Schier
2025-11-04 08:44:08 -08:00
parent 81ceb981e8
commit 0cb633e479
11 changed files with 301 additions and 401 deletions

View File

@@ -1,49 +1,36 @@
import {
enableEncryption,
revealWorkspaceKey,
setWorkspaceKey,
} from "@yaakapp-internal/crypto";
import type { WorkspaceMeta } from "@yaakapp-internal/models";
import classNames from "classnames";
import { useAtomValue } from "jotai";
import { useEffect, useState } from "react";
import {
activeWorkspaceAtom,
activeWorkspaceMetaAtom,
} from "../hooks/useActiveWorkspace";
import { createFastMutation } from "../hooks/useFastMutation";
import { useStateWithDeps } from "../hooks/useStateWithDeps";
import { CopyIconButton } from "./CopyIconButton";
import { Banner } from "./core/Banner";
import type { ButtonProps } from "./core/Button";
import { Button } from "./core/Button";
import { IconButton } from "./core/IconButton";
import { IconTooltip } from "./core/IconTooltip";
import { Label } from "./core/Label";
import { PlainInput } from "./core/PlainInput";
import { HStack, VStack } from "./core/Stacks";
import { EncryptionHelp } from "./EncryptionHelp";
import { enableEncryption, revealWorkspaceKey, setWorkspaceKey } from '@yaakapp-internal/crypto';
import type { WorkspaceMeta } from '@yaakapp-internal/models';
import classNames from 'classnames';
import { useAtomValue } from 'jotai';
import { useEffect, useState } from 'react';
import { activeWorkspaceAtom, activeWorkspaceMetaAtom } from '../hooks/useActiveWorkspace';
import { createFastMutation } from '../hooks/useFastMutation';
import { useStateWithDeps } from '../hooks/useStateWithDeps';
import { CopyIconButton } from './CopyIconButton';
import { Banner } from './core/Banner';
import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
import { IconButton } from './core/IconButton';
import { IconTooltip } from './core/IconTooltip';
import { Label } from './core/Label';
import { PlainInput } from './core/PlainInput';
import { HStack, VStack } from './core/Stacks';
import { EncryptionHelp } from './EncryptionHelp';
interface Props {
size?: ButtonProps["size"];
size?: ButtonProps['size'];
expanded?: boolean;
onDone?: () => void;
onEnabledEncryption?: () => void;
}
export function WorkspaceEncryptionSetting(
{ size, expanded, onDone, onEnabledEncryption }: Props,
) {
const [justEnabledEncryption, setJustEnabledEncryption] = useState<boolean>(
false,
);
export function WorkspaceEncryptionSetting({ size, expanded, onDone, onEnabledEncryption }: Props) {
const [justEnabledEncryption, setJustEnabledEncryption] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const workspace = useAtomValue(activeWorkspaceAtom);
const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom);
const [key, setKey] = useState<
{ key: string | null; error: string | null } | null
>(null);
const [key, setKey] = useState<{ key: string | null; error: string | null } | null>(null);
useEffect(() => {
if (workspaceMeta == null) {
@@ -122,7 +109,7 @@ export function WorkspaceEncryptionSetting(
return (
<div className="mb-auto flex flex-col-reverse">
<Button
color={expanded ? "info" : "secondary"}
color={expanded ? 'info' : 'secondary'}
size={size}
onClick={async () => {
setError(null);
@@ -130,30 +117,32 @@ export function WorkspaceEncryptionSetting(
await enableEncryption(workspaceMeta.workspaceId);
setJustEnabledEncryption(true);
} catch (err) {
setError("Failed to enable encryption: " + err);
setError('Failed to enable encryption: ' + err);
}
}}
>
Enable Encryption
</Button>
{error && <Banner color="danger" className="mb-2">{error}</Banner>}
{expanded
? (
<Banner color="info" className="mb-6">
<EncryptionHelp />
</Banner>
)
: (
<Label htmlFor={null} help={<EncryptionHelp />}>
Workspace encryption
</Label>
)}
{error && (
<Banner color="danger" className="mb-2">
{error}
</Banner>
)}
{expanded ? (
<Banner color="info" className="mb-6">
<EncryptionHelp />
</Banner>
) : (
<Label htmlFor={null} help={<EncryptionHelp />}>
Workspace encryption
</Label>
)}
</div>
);
}
const setWorkspaceKeyMut = createFastMutation({
mutationKey: ["set-workspace-key"],
mutationKey: ['set-workspace-key'],
mutationFn: setWorkspaceKey,
});
@@ -166,13 +155,15 @@ function EnterWorkspaceKey({
onEnabled?: () => void;
error?: string | null;
}) {
const [key, setKey] = useState<string>("");
const [key, setKey] = useState<string>('');
return (
<VStack space={4} className="w-full">
{error ? <Banner color="danger">{error}</Banner> : (
{error ? (
<Banner color="danger">{error}</Banner>
) : (
<Banner color="info">
This workspace contains encrypted values but no key is configured.
Please enter the workspace key to access the encrypted data.
This workspace contains encrypted values but no key is configured. Please enter the
workspace key to access the encrypted data.
</Banner>
)}
<HStack
@@ -219,35 +210,24 @@ function KeyRevealer({
return (
<div
className={classNames(
"w-full border border-border rounded-md pl-3 py-2 p-1",
"grid gap-1 grid-cols-[minmax(0,1fr)_auto] items-center",
'w-full border border-border rounded-md pl-3 py-2 p-1',
'grid gap-1 grid-cols-[minmax(0,1fr)_auto] items-center',
)}
>
<VStack space={0.5}>
{!disableLabel && (
<span className="text-sm text-primary flex items-center gap-1">
Workspace encryption key{" "}
<IconTooltip
iconSize="sm"
size="lg"
content={helpAfterEncryption}
/>
Workspace encryption key{' '}
<IconTooltip iconSize="sm" size="lg" content={helpAfterEncryption} />
</span>
)}
{encryptionKey && (
<HighlightedKey
keyText={encryptionKey}
show={show}
/>
)}
{encryptionKey && <HighlightedKey keyText={encryptionKey} show={show} />}
</VStack>
<HStack>
{encryptionKey && (
<CopyIconButton text={encryptionKey} title="Copy workspace key" />
)}
{encryptionKey && <CopyIconButton text={encryptionKey} title="Copy workspace key" />}
<IconButton
title={show ? "Hide" : "Reveal" + "workspace key"}
icon={show ? "eye_closed" : "eye"}
title={show ? 'Hide' : 'Reveal' + 'workspace key'}
icon={show ? 'eye_closed' : 'eye'}
onClick={() => setShow((v) => !v)}
/>
</HStack>
@@ -258,32 +238,31 @@ function KeyRevealer({
function HighlightedKey({ keyText, show }: { keyText: string; show: boolean }) {
return (
<span className="text-xs font-mono [&_*]:cursor-auto [&_*]:select-text">
{show
? (
keyText.split("").map((c, i) => {
return (
<span
key={i}
className={classNames(
c.match(/[0-9]/) && "text-info",
c == "-" && "text-text-subtle",
)}
>
{c}
</span>
);
})
)
: <div className="text-text-subtle"></div>}
{show ? (
keyText.split('').map((c, i) => {
return (
<span
key={i}
className={classNames(
c.match(/[0-9]/) && 'text-info',
c == '-' && 'text-text-subtle',
)}
>
{c}
</span>
);
})
) : (
<div className="text-text-subtle"></div>
)}
</span>
);
}
const helpAfterEncryption = (
<p>
The following key is used for encryption operations within this workspace.
It is stored securely using your OS keychain, but it is recommended to back
it up. If you share this workspace with others, you&apos;ll need to send
them this key to access any encrypted values.
The following key is used for encryption operations within this workspace. It is stored securely
using your OS keychain, but it is recommended to back it up. If you share this workspace with
others, you&apos;ll need to send them this key to access any encrypted values.
</p>
);