fix(transactions): avoid crypto.randomUUID on initial load

This commit is contained in:
Felipe Coutinho
2026-03-26 14:18:47 +00:00
parent 0bd9d0ac47
commit 32da4f906e
5 changed files with 63 additions and 29 deletions

View File

@@ -7,6 +7,12 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR
## [Unreleased] ## [Unreleased]
## [2.0.3] - 2026-03-26
### Corrigido
- Lançamentos: `/transactions` deixa de depender de `crypto.randomUUID()` no carregamento inicial, corrigindo a falha em ambientes self-hosted sem HTTPS ao abrir a página
## [2.0.2] - 2026-03-25 ## [2.0.2] - 2026-03-25
### Adicionado ### Adicionado

View File

@@ -1,6 +1,6 @@
{ {
"name": "openmonetis", "name": "openmonetis",
"version": "2.0.2", "version": "2.0.3",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "next dev --turbopack",

View File

@@ -38,6 +38,7 @@ import {
import { Separator } from "@/shared/components/ui/separator"; import { Separator } from "@/shared/components/ui/separator";
import { Spinner } from "@/shared/components/ui/spinner"; import { Spinner } from "@/shared/components/ui/spinner";
import { getTodayDateString } from "@/shared/utils/date"; import { getTodayDateString } from "@/shared/utils/date";
import { createClientSafeId } from "@/shared/utils/id";
import { import {
dateToPeriod, dateToPeriod,
displayPeriod, displayPeriod,
@@ -120,6 +121,19 @@ interface TransactionRow {
payerId: string | undefined; payerId: string | undefined;
} }
function createEmptyTransactionRow(
defaultPayerId?: string | null,
): TransactionRow {
return {
id: createClientSafeId(),
purchaseDate: getTodayDateString(),
name: "",
amount: "",
categoryId: undefined,
payerId: defaultPayerId ?? undefined,
};
}
export function MassAddDialog({ export function MassAddDialog({
open, open,
onOpenChange, onOpenChange,
@@ -153,15 +167,8 @@ export function MassAddDialog({
const isCartaoSelected = paymentMethod === "Cartão de crédito"; const isCartaoSelected = paymentMethod === "Cartão de crédito";
// Transaction rows // Transaction rows
const [transactions, setTransactions] = useState<TransactionRow[]>([ const [transactions, setTransactions] = useState<TransactionRow[]>(() => [
{ createEmptyTransactionRow(defaultPayerId),
id: crypto.randomUUID(),
purchaseDate: getTodayDateString(),
name: "",
amount: "",
categoryId: undefined,
payerId: defaultPayerId ?? undefined,
},
]); ]);
// Categorias agrupadas e filtradas por tipo de transação // Categorias agrupadas e filtradas por tipo de transação
@@ -175,14 +182,7 @@ export function MassAddDialog({
const addTransaction = () => { const addTransaction = () => {
setTransactions([ setTransactions([
...transactions, ...transactions,
{ createEmptyTransactionRow(defaultPayerId),
id: crypto.randomUUID(),
purchaseDate: getTodayDateString(),
name: "",
amount: "",
categoryId: undefined,
payerId: defaultPayerId ?? undefined,
},
]); ]);
}; };
@@ -256,16 +256,7 @@ export function MassAddDialog({
setPeriod(selectedPeriod); setPeriod(selectedPeriod);
setContaId(undefined); setContaId(undefined);
setCartaoId(defaultCardId ?? undefined); setCartaoId(defaultCardId ?? undefined);
setTransactions([ setTransactions([createEmptyTransactionRow(defaultPayerId)]);
{
id: crypto.randomUUID(),
purchaseDate: getTodayDateString(),
name: "",
amount: "",
categoryId: undefined,
payerId: defaultPayerId ?? undefined,
},
]);
} catch (_error) { } catch (_error) {
// Error is handled by the onSubmit function // Error is handled by the onSubmit function
} finally { } finally {

View File

@@ -575,7 +575,7 @@ export function TransactionsPage({
onConfirm={handleBulkEdit} onConfirm={handleBulkEdit}
/> />
{allowCreate ? ( {allowCreate && massAddOpen ? (
<MassAddDialog <MassAddDialog
open={massAddOpen} open={massAddOpen}
onOpenChange={setMassAddOpen} onOpenChange={setMassAddOpen}

37
src/shared/utils/id.ts Normal file
View File

@@ -0,0 +1,37 @@
const FALLBACK_HEX_RADIX = 16;
function randomHex(byteCount: number) {
const cryptoApi = globalThis.crypto;
if (cryptoApi?.getRandomValues) {
return Array.from(cryptoApi.getRandomValues(new Uint8Array(byteCount)))
.map((byte) => byte.toString(FALLBACK_HEX_RADIX).padStart(2, "0"))
.join("");
}
let hex = "";
for (let index = 0; index < byteCount; index += 1) {
hex += Math.floor(Math.random() * 256)
.toString(FALLBACK_HEX_RADIX)
.padStart(2, "0");
}
return hex;
}
export function createClientSafeId() {
const cryptoApi = globalThis.crypto;
if (cryptoApi?.randomUUID) {
return cryptoApi.randomUUID();
}
return [
randomHex(4),
randomHex(2),
randomHex(2),
randomHex(2),
randomHex(6),
].join("-");
}