Fix history for the 100th time. Fix role resolution in the invitation screen
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -350,29 +350,32 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
const indicator = isFocused ? '▸ ' : ' ';
|
||||
const groupingPrefix = row.isNested ? ' -> ' : '';
|
||||
|
||||
if (row.type === 'invitation') {
|
||||
if (row.type === 'history_item') {
|
||||
const sats = row.valueSatoshis ?? 0n;
|
||||
const fiatSuffix = getFiatSuffix(sats);
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="space-between">
|
||||
<Box flexDirection="row">
|
||||
<Text color={itemColor}>
|
||||
{indicator}[Invitation] {row.label}
|
||||
{indicator}{formatSatoshis(sats)}{fiatSuffix}
|
||||
</Text>
|
||||
<Text color={colors.textMuted}> {row.label}</Text>
|
||||
</Box>
|
||||
{dateStr && <Text color={colors.textMuted}>{dateStr}</Text>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (row.type === 'invitation_input') {
|
||||
const inputSatoshis = row.utxo?.valueSatoshis;
|
||||
const inputFiatSuffix = inputSatoshis !== undefined
|
||||
? getFiatSuffix(inputSatoshis)
|
||||
: '';
|
||||
if (row.type === 'history_input') {
|
||||
const sats = row.valueSatoshis ?? 0n;
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="space-between">
|
||||
<Box>
|
||||
<Text color={itemColor}>
|
||||
{indicator}{groupingPrefix}[Input] {row.label}
|
||||
{inputFiatSuffix}
|
||||
{indicator}{groupingPrefix}[Input] {formatSatoshis(sats)}
|
||||
{getFiatSuffix(sats)}
|
||||
</Text>
|
||||
<Text color={colors.textMuted}> {row.label}</Text>
|
||||
{row.description && <Text color={colors.textMuted}> {row.description}</Text>}
|
||||
</Box>
|
||||
{dateStr && <Text color={colors.textMuted}>{dateStr}</Text>}
|
||||
@@ -380,8 +383,9 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (row.type === 'invitation_output') {
|
||||
const sats = row.utxo?.valueSatoshis ?? 0n;
|
||||
if (row.type === 'history_output') {
|
||||
const sats = row.valueSatoshis ?? 0n;
|
||||
const reservedTag = row.reserved ? ' [Reserved]' : '';
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="space-between">
|
||||
<Box flexDirection="row">
|
||||
@@ -389,6 +393,7 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
{indicator}{groupingPrefix}[Output] {formatSatoshis(sats)}
|
||||
{getFiatSuffix(sats)}
|
||||
</Text>
|
||||
<Text color={colors.textMuted}> {row.label}{reservedTag}</Text>
|
||||
{row.description && <Text color={colors.textMuted}> {row.description}</Text>}
|
||||
</Box>
|
||||
{dateStr && <Text color={colors.textMuted}>{dateStr}</Text>}
|
||||
@@ -396,23 +401,6 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (row.type === 'utxo') {
|
||||
const sats = row.utxo?.valueSatoshis ?? 0n;
|
||||
const reservedTag = row.utxo?.reserved ? ' [Reserved]' : '';
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="space-between">
|
||||
<Box flexDirection="row">
|
||||
<Text color={itemColor}>
|
||||
{indicator}{formatSatoshis(sats)}
|
||||
{getFiatSuffix(sats)}
|
||||
</Text>
|
||||
{row.description && <Text color={colors.textMuted}> {row.description}{reservedTag}</Text>}
|
||||
</Box>
|
||||
{dateStr && <Text color={colors.textMuted}>{dateStr}</Text>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback for other types
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="space-between">
|
||||
@@ -515,7 +503,7 @@ export function WalletStateScreen(): React.ReactElement {
|
||||
height={14}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Text color={colors.primary} bold> Wallet History {history.length > 0 ? `(${selectedHistoryIndex + 1}/${history.length})` : ''}</Text>
|
||||
<Text color={colors.primary} bold> Wallet History {historyListItems.length > 0 ? `(${selectedHistoryIndex + 1}/${historyListItems.length})` : ''}</Text>
|
||||
{isLoading ? (
|
||||
<Box marginTop={1}>
|
||||
<Text color={colors.textMuted}>Loading...</Text>
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useSatoshisConversion } from '../../hooks/useSatoshisConversion.js';
|
||||
import { colors, logoSmall, formatSatoshis } from '../../theme.js';
|
||||
import { copyToClipboard } from '../../utils/clipboard.js';
|
||||
import type { Invitation } from '../../../services/invitation.js';
|
||||
import type { XOTemplate } from '@xo-cash/types';
|
||||
import type { XOInvitationCommit, XOTemplate } from '@xo-cash/types';
|
||||
|
||||
import {
|
||||
getInvitationState,
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
getInvitationInputs,
|
||||
getInvitationOutputs,
|
||||
getInvitationVariables,
|
||||
getUserRole,
|
||||
formatInvitationListItem,
|
||||
formatInvitationId,
|
||||
} from '../../../utils/invitation-utils.js';
|
||||
@@ -80,6 +79,29 @@ const invitationListGroups: ListGroup[] = [
|
||||
{ id: 'invitations', separator: true },
|
||||
];
|
||||
|
||||
type OwnInvitationContext = {
|
||||
entityIdentifier: string | null;
|
||||
roleIdentifier: string | null;
|
||||
};
|
||||
|
||||
function getRoleIdentifierFromCommits(commits: XOInvitationCommit[]): string | null {
|
||||
for (const commit of commits) {
|
||||
for (const input of commit.data.inputs ?? []) {
|
||||
if (input.roleIdentifier) return input.roleIdentifier;
|
||||
}
|
||||
|
||||
for (const output of commit.data.outputs ?? []) {
|
||||
if (output.roleIdentifier) return output.roleIdentifier;
|
||||
}
|
||||
|
||||
for (const variable of commit.data.variables ?? []) {
|
||||
if (variable.roleIdentifier) return variable.roleIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invitation Screen Component.
|
||||
*/
|
||||
@@ -107,6 +129,10 @@ export function InvitationScreen(): React.ReactElement {
|
||||
// ── Template cache ───────────────────────────────────────────────────────
|
||||
const [templateCache, setTemplateCache] = useState<Map<string, XOTemplate>>(new Map());
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<XOTemplate | null>(null);
|
||||
const [ownInvitationContext, setOwnInvitationContext] = useState<OwnInvitationContext>({
|
||||
entityIdentifier: null,
|
||||
roleIdentifier: null,
|
||||
});
|
||||
|
||||
// Check if we should open import dialog on mount
|
||||
const initialMode = navData.mode as string | undefined;
|
||||
@@ -180,6 +206,43 @@ export function InvitationScreen(): React.ReactElement {
|
||||
.then(template => setSelectedTemplate(template ?? null));
|
||||
}, [selectedInvitation, appService]);
|
||||
|
||||
/**
|
||||
* Load the current engine entity's commits for the selected invitation.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!selectedInvitation || !appService) {
|
||||
setOwnInvitationContext({
|
||||
entityIdentifier: null,
|
||||
roleIdentifier: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let isCurrent = true;
|
||||
|
||||
appService.engine.getOwnCommits(selectedInvitation.data)
|
||||
.then((ownCommits) => {
|
||||
if (!isCurrent) return;
|
||||
|
||||
setOwnInvitationContext({
|
||||
entityIdentifier: ownCommits[0]?.entityIdentifier ?? null,
|
||||
roleIdentifier: getRoleIdentifierFromCommits(ownCommits),
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
if (!isCurrent) return;
|
||||
|
||||
setOwnInvitationContext({
|
||||
entityIdentifier: null,
|
||||
roleIdentifier: null,
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
isCurrent = false;
|
||||
};
|
||||
}, [selectedInvitation, appService]);
|
||||
|
||||
// ── Import flow callbacks ──────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
@@ -512,9 +575,8 @@ export function InvitationScreen(): React.ReactElement {
|
||||
const inputs = getInvitationInputs(selectedInvitation);
|
||||
const outputs = getInvitationOutputs(selectedInvitation);
|
||||
const variables = getInvitationVariables(selectedInvitation);
|
||||
|
||||
const userEntityId = selectedInvitation.data.commits?.[0]?.entityIdentifier ?? null;
|
||||
const userRole = getUserRole(selectedInvitation, userEntityId);
|
||||
const userEntityId = ownInvitationContext.entityIdentifier;
|
||||
const userRole = ownInvitationContext.roleIdentifier;
|
||||
const roleInfoRaw = userRole && selectedTemplate?.roles?.[userRole];
|
||||
const roleInfo = roleInfoRaw && typeof roleInfoRaw === 'object' ? roleInfoRaw : null;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type {
|
||||
HistoryItem,
|
||||
HistoryInvitationItem,
|
||||
HistoryUtxoItem,
|
||||
WalletHistoryInput,
|
||||
WalletHistoryItem,
|
||||
WalletHistoryOutput,
|
||||
} from "../services/history.js";
|
||||
|
||||
export type HistoryColorName =
|
||||
@@ -13,10 +14,9 @@ export type HistoryColorName =
|
||||
| "text";
|
||||
|
||||
export type HistoryRowType =
|
||||
| "invitation"
|
||||
| "invitation_input"
|
||||
| "invitation_output"
|
||||
| "utxo";
|
||||
| "history_item"
|
||||
| "history_input"
|
||||
| "history_output";
|
||||
|
||||
export interface HistoryDisplayRow {
|
||||
id: string;
|
||||
@@ -25,8 +25,11 @@ export interface HistoryDisplayRow {
|
||||
description?: string;
|
||||
timestamp?: number;
|
||||
isNested: boolean;
|
||||
utxo?: HistoryUtxoItem;
|
||||
invitation?: HistoryInvitationItem;
|
||||
valueSatoshis?: bigint;
|
||||
reserved?: boolean;
|
||||
input?: WalletHistoryInput;
|
||||
output?: WalletHistoryOutput;
|
||||
item?: WalletHistoryItem;
|
||||
}
|
||||
|
||||
export function formatHistoryDate(timestamp?: number): string | undefined {
|
||||
@@ -40,61 +43,68 @@ export function buildHistoryDisplayRows(
|
||||
const rows: HistoryDisplayRow[] = [];
|
||||
|
||||
for (const item of items) {
|
||||
if (item.kind === "invitation") {
|
||||
const roles = item.roles.length > 0 ? item.roles.join(", ") : "unknown";
|
||||
if (item.source === "utxo") {
|
||||
for (const output of item.outputs) {
|
||||
rows.push({
|
||||
id: item.id,
|
||||
type: "invitation",
|
||||
label: item.description,
|
||||
id: `${item.id}-output-${output.id}`,
|
||||
type: "history_output",
|
||||
label: output.outpoint
|
||||
? `${output.outpoint.txid}:${output.outpoint.index}`
|
||||
: output.outputIdentifier ?? "Output",
|
||||
description: `${item.template} | ${roles} | ${output.description}`,
|
||||
timestamp: item.createdAtTimestamp,
|
||||
isNested: false,
|
||||
invitation: item,
|
||||
valueSatoshis: output.valueSatoshis,
|
||||
reserved: output.reserved,
|
||||
output,
|
||||
item,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
rows.push({
|
||||
id: item.id,
|
||||
type: "history_item",
|
||||
label: `${item.template} | ${roles} | ${item.description}`,
|
||||
description: item.action,
|
||||
timestamp: item.createdAtTimestamp,
|
||||
isNested: false,
|
||||
valueSatoshis: item.valueSatoshis,
|
||||
item,
|
||||
});
|
||||
|
||||
if (item.source !== "invitation") continue;
|
||||
|
||||
for (const input of item.inputs) {
|
||||
const satsPrefix =
|
||||
input.valueSatoshis !== undefined
|
||||
? `${input.valueSatoshis.toLocaleString()} sats `
|
||||
: "";
|
||||
rows.push({
|
||||
id: `${item.id}-input-${input.id}`,
|
||||
type: "invitation_input",
|
||||
label: `${satsPrefix}${input.outpoint.txid}:${input.outpoint.index}`,
|
||||
type: "history_input",
|
||||
label: `${input.outpoint.txid}:${input.outpoint.index}`,
|
||||
description: input.description,
|
||||
isNested: true,
|
||||
utxo: input,
|
||||
invitation: item,
|
||||
valueSatoshis: input.valueSatoshis,
|
||||
input,
|
||||
item,
|
||||
});
|
||||
}
|
||||
|
||||
for (const output of item.outputs) {
|
||||
rows.push({
|
||||
id: `${item.id}-output-${output.id}`,
|
||||
type: "invitation_output",
|
||||
label:
|
||||
output.valueSatoshis !== undefined
|
||||
? `${output.valueSatoshis.toLocaleString()} sats`
|
||||
: "Output",
|
||||
type: "history_output",
|
||||
label: output.outpoint
|
||||
? `${output.outpoint.txid}:${output.outpoint.index}`
|
||||
: output.outputIdentifier ?? "Output",
|
||||
description: output.description,
|
||||
isNested: true,
|
||||
utxo: output,
|
||||
invitation: item,
|
||||
valueSatoshis: output.valueSatoshis,
|
||||
reserved: output.reserved,
|
||||
output,
|
||||
item,
|
||||
});
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
rows.push({
|
||||
id: item.id,
|
||||
type: "utxo",
|
||||
label:
|
||||
item.valueSatoshis !== undefined
|
||||
? `${item.valueSatoshis.toLocaleString()} sats`
|
||||
: "UTXO",
|
||||
description: item.description,
|
||||
isNested: false,
|
||||
utxo: item,
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
@@ -106,14 +116,14 @@ export function getHistoryItemColorName(
|
||||
): HistoryColorName {
|
||||
if (isSelected) return "info";
|
||||
switch (row.type) {
|
||||
case "invitation":
|
||||
return "text";
|
||||
case "invitation_input":
|
||||
case "history_input":
|
||||
return "error";
|
||||
case "invitation_output":
|
||||
return "success";
|
||||
case "utxo":
|
||||
return row.utxo?.reserved ? "warning" : "success";
|
||||
case "history_output":
|
||||
return row.reserved ? "warning" : "success";
|
||||
case "history_item":
|
||||
if ((row.valueSatoshis ?? 0n) < 0n) return "error";
|
||||
if ((row.valueSatoshis ?? 0n) > 0n) return "success";
|
||||
return "text";
|
||||
default:
|
||||
return "text";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user