diff --git a/assets/file-text.png b/assets/file-text.png new file mode 100644 index 000000000..dd6095947 Binary files /dev/null and b/assets/file-text.png differ diff --git a/assets/pencil.png b/assets/pencil.png new file mode 100644 index 000000000..46a769db1 Binary files /dev/null and b/assets/pencil.png differ diff --git a/components/Transaction/InvoiceModal.tsx b/components/Transaction/InvoiceModal.tsx new file mode 100644 index 000000000..82fcb6394 --- /dev/null +++ b/components/Transaction/InvoiceModal.tsx @@ -0,0 +1,240 @@ +import React, { useState, useEffect, ReactElement } from 'react' +import style from './transaction.module.css' +import Button from 'components/Button' +import { CreateInvoicePOSTParameters } from 'utils/validators' +import axios from 'axios' +import { Prisma } from '@prisma/client' + +export interface InvoiceData { + id?: string + invoiceNumber: Prisma.Decimal + amount: number + recipientName: string + recipientAddress: string + description: string + customerName: string + customerAddress: string +} + +interface InvoiceModalProps { + isOpen: boolean + onClose: () => void + transaction: any + invoiceData: InvoiceData | null + mode: 'create' | 'edit' | 'view' +} + +export default function InvoiceModal ({ + isOpen, + onClose, + invoiceData, + transaction, + mode +}: InvoiceModalProps): ReactElement | null { + const [formData, setFormData] = useState({ + invoiceNumber: '', + amount: Number(transaction?.amount), + recipientName: '', + recipientAddress: transaction?.address?.address, + description: '', + customerName: '', + customerAddress: '' + }) + + useEffect(() => { + setFormData(invoiceData ?? { + invoiceNumber: '', + amount: Number(transaction?.amount), + recipientName: '', + recipientAddress: transaction?.address?.address, + description: '', + customerName: '', + customerAddress: '' + }) + }, [transaction, mode, invoiceData]) + + if (!isOpen) return null + + const handleChange = (e: React.ChangeEvent): void => { + const { name, value } = e.target + setFormData(prev => ({ ...prev, [name]: value })) + } + + const handleModalClose = (): void => { + setFormData({ + invoiceNumber: '', + amount: 0, + recipientName: '', + recipientAddress: '', + description: '', + customerName: '', + customerAddress: '' + }) + onClose() + } + + async function handleSubmit (e: React.FormEvent): Promise { + e.preventDefault() + + if (mode === 'edit') { + await updateInvoice() + } else { + await createInvoice() + } + onClose() + } + async function createInvoice (): Promise { + const payload: CreateInvoicePOSTParameters = { + ...formData, + transactionId: transaction?.id + } + + try { + await axios.post('/api/invoices', payload) + } catch (err: any) { + console.error('Invoice submission error:', err) + } + } + + async function updateInvoice (): Promise { + const payload: CreateInvoicePOSTParameters = { + ...formData, + transactionId: transaction?.id + } + + try { + await axios.put(`/api/invoices/?invoiceId=${invoiceData?.id ?? ''}`, payload) + onClose() + } catch (err: any) { + console.error('Invoice update error:', err) + } + } + const isReadOnly = mode === 'view' + + return ( +
+
+

{mode === 'edit' ? 'Edit Invoice' : mode === 'view' ? 'View Invoice' : 'Create Invoice'}

+
+ {!isReadOnly + ?
{ + void handleSubmit(e) + }} method="post"> +
+
+ + +
+
+ + +
+
+ + + + + + + + + + + + + + + +
+
+ +
+ {!isReadOnly && ( +
+ +
+ )} +
+
+ :
+
+
+ Invoice Number: {formData.invoiceNumber} +
+
+ Amount: {formData.amount} +
+
+ Recipient Name: {formData.recipientName} +
+
+ Recipient Address: {formData.recipientAddress} +
+
+ Description: {formData.description} +
+
+ Customer Name: {formData.customerName} +
+
+ Customer Address: {formData.customerAddress} +
+
+
+ +
+
+ } +
+
+
+ ) +} diff --git a/components/Transaction/PaybuttonTransactions.tsx b/components/Transaction/PaybuttonTransactions.tsx index db6fa90b5..e05814d49 100644 --- a/components/Transaction/PaybuttonTransactions.tsx +++ b/components/Transaction/PaybuttonTransactions.tsx @@ -1,14 +1,21 @@ -import React, { useMemo } from 'react' +import React, { useMemo, useState } from 'react' import Image from 'next/image' import XECIcon from 'assets/xec-logo.png' import BCHIcon from 'assets/bch-logo.png' import EyeIcon from 'assets/eye-icon.png' import CheckIcon from 'assets/check-icon.png' import XIcon from 'assets/x-icon.png' +import Plus from 'assets/plus.png' +import Pencil from 'assets/pencil.png' +import FileText from 'assets/file-text.png' + import TableContainerGetter from '../TableContainer/TableContainerGetter' import { compareNumericString } from 'utils/index' import moment from 'moment-timezone' import { XEC_TX_EXPLORER_URL, BCH_TX_EXPLORER_URL } from 'constants/index' +import InvoiceModal, { InvoiceData } from './InvoiceModal' +import style from './transaction.module.css' +import { TransactionWithAddressAndPricesAndInvoices } from 'services/transactionService' interface IProps { addressSyncing: { @@ -36,7 +43,58 @@ function fetchTransactionsByPaybuttonId (paybuttonId: string): Function { } } +const fetchNextInvoiceNumberByUserId = async (): Promise => { + const response = await fetch('/api/invoices/invoiceNumber/', { + headers: { + Timezone: moment.tz.guess() + } + }) + const result = await response?.json() + return result?.invoiceNumber +} + export default ({ paybuttonId, addressSyncing, tableRefreshCount, timezone = moment.tz.guess() }: IProps): JSX.Element => { + const [isModalOpen, setIsModalOpen] = useState(false) + const [invoiceData, setInvoiceData] = useState(null) + const [invoiceDataTransaction, setInvoiceDataTransaction] = useState(null) + const [localRefreshCount, setLocalRefreshCount] = useState(tableRefreshCount) + + const [invoiceMode, setInvoiceMode] = useState<'create' | 'edit' | 'view'>('create') + + const onCreateInvoice = async (transaction: TransactionWithAddressAndPricesAndInvoices): Promise => { + const nextInvoiceNumber = await fetchNextInvoiceNumberByUserId() + const invoiceData = { + invoiceNumber: nextInvoiceNumber ?? '', + amount: Number(transaction.amount), + recipientName: '', + recipientAddress: transaction.address.address, + description: '', + customerName: '', + customerAddress: '' + } + setInvoiceDataTransaction(transaction) + setInvoiceData(invoiceData) + setInvoiceMode('create') + setIsModalOpen(true) + } + + const onEditInvoice = (invoiceData: InvoiceData): void => { + setInvoiceData(invoiceData) + setInvoiceMode('edit') + setIsModalOpen(true) + } + + const onSeeInvoice = (invoiceData: InvoiceData): void => { + setInvoiceData(invoiceData) + setInvoiceMode('view') + setIsModalOpen(true) + } + + const handleCloseModal = (): void => { + setIsModalOpen(false) + setInvoiceData(null) + setLocalRefreshCount(prev => prev + 1) + } const columns = useMemo( () => [ { @@ -109,13 +167,76 @@ export default ({ paybuttonId, addressSyncing, tableRefreshCount, timezone = mom ) } + }, + { + Header: () => (
Actions
), + id: 'actions', + Cell: (cellProps) => { + const invoices = cellProps.row.original.invoices + const hasInvoice = invoices?.length > 0 + + return ( +
+ {!hasInvoice + ? ( +
+ +
New button
+
+ ) + : ( + <> +
+ +
+
+ +
+ + )} +
+ ) + } } + ], [] ) return ( <> - + + ) } diff --git a/components/Transaction/transaction.module.css b/components/Transaction/transaction.module.css index 043235d0f..dff9198fc 100644 --- a/components/Transaction/transaction.module.css +++ b/components/Transaction/transaction.module.css @@ -132,3 +132,240 @@ font-size: 12px; } } + +.form_ctn_outer { + background-color: rgba(255, 255, 255, 0.3); + backdrop-filter: blur(5px); + position: fixed; + top: 0; + left: 0px; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + z-index: 99999; +} + +.form_ctn_inner { + width: 100%; + max-width: 600px; + align-self: middle; +} + +.form_ctn_inner h4 { + margin: 0; + margin-left: 5px; + margin-bottom: 5px; +} +.form_ctn { + background-color: var(--secondary-bg-color); + width: 100%; + padding: 25px; + border-radius: 10px; + border: 1px solid var(--secondary-text-color); + overflow-y: auto; + max-height: 90vh; +} +body[data-theme='dark'] .form_ctn_outer { + background-color: rgba(0, 0, 0, 0.3); +} + +.form_ctn p { + margin-top: 0; +} + +.form_ctn form { + display: flex; + flex-direction: column; +} + +.form_ctn label { + margin-bottom: 5px; +} + +body[data-theme='dark'] .form_ctn select { + color: #fff; +} + +.form_ctn select { + background-color: var(--secondary-bg-color); +} + +.form_ctn input, +.form_ctn textarea { + border: 1px solid var(--secondary-text-color) !important; + width: 100%; +} + +body[data-theme='dark'] .form_ctn input, +body[data-theme='dark'] .form_ctn textarea, +body[data-theme='dark'] .form_ctn select { + background-color: var(--primary-bg-color); + border-color: #898EA4 !important; +} + +body[data-theme='dark'] .form_ctn select:not([multiple]) { + background-image: linear-gradient(45deg,transparent 49%,#fff 51%),linear-gradient(135deg,#fff 51%,transparent 49%); +} + +.labelMargin { + margin-top: 10px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.form_ctn button:last-child { + margin-right: 0px; + box-shadow: none; +} + +.form_ctn .delete_btn { + background-color: unset; + border: none; + margin-right: 0px; + margin-left: 10px; + color: var(--primary-text-color); + border-radius: 0px; + padding-left: 0px; + padding-right: 0px; + opacity: 0.6; + position: relative; + transition: all ease-in-out 100ms; +} + +.form_ctn .delete_btn:hover { + color: red; + opacity: 1; +} + +.form_ctn .delete_btn div { + width: 15px; + border-radius: 0px !important; + position: absolute; + right: 0px; + top: 12px; + opacity: -10; + transition: all ease-in-out 100ms; +} + +.form_ctn .delete_btn:hover div { + right: -18px; + opacity: 1; +} + +.form_ctn .delete_confirm_btn { + padding-left: 20px; + padding-right: 20px; + border: 1px solid var(--primary-text-color); + background-color: var(--primary-bg-color); + color: red; + transition: all ease-in-out 100ms; + font-weight: 600; +} + +.delete_button_form_ctn { + word-break: normal; +} + +.form_ctn .delete_confirm_btn:hover { + border: 1px solid var(--primary-text-color); + background-color: rgb(149, 0, 0); + color: var(--secondary-bg-color); +} +.tooltiptext { + visibility: hidden; + opacity: 0; + background-color: var(--secondary-bg-color); + color: var(--primary-text-color); + text-align: center; + padding: 2px 0px; + border-radius: 5px; + position: absolute; + z-index: 1; + bottom: -30px; + width: 80px; + right: -5px; + flex-basis: 1; + font-size: 12px; + z-index: 99999999; + box-sizing: border-box; + transition: all 100ms ease-in-out; +} + +.tooltiptext::before { + content: ''; + position: absolute; + left: 50%; + width: 0; + height: 0; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + transform: translate(-50%, -9px); + border-bottom: 7px solid var(--secondary-bg-color); +} + +.create_invoice:hover .tooltiptext { + visibility: visible; + bottom: -35px; + opacity: 1; +} + +.create_invoice_ctn { + position: relative; + position: sticky; + bottom: 0; + margin-left: auto; + float: right; +} + +.create_invoice { + background-color: var(--secondary-bg-color); + font-size: 44px; + display: inline-block; + border-radius: 100px; + padding: 20px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all ease-in-out 100ms; + position: relative; + border: 1px solid var(--secondary-text-color); +} + +.invoice_view_item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; + border-bottom: 1px solid var(--primary-text-color); +} + +body[data-theme='dark'] .create_invoice img { + filter: invert(1); +} + +body[data-theme='dark'] .edit_invoice img { + filter: invert(1); +} + +body[data-theme='dark'] .see_invoice img { + filter: invert(1); +} + +.create_invoice_ctn:hover .create_invoice { + filter: invert(60%) sepia(14%) saturate(5479%) hue-rotate(195deg) + brightness(100%) contrast(102%); +} + +.edit_invoice_ctn:hover .edit_invoice{ + filter: invert(60%) sepia(14%) saturate(5479%) hue-rotate(195deg) + brightness(100%) contrast(102%); +} + +.see_invoice_ctn:hover .see_invoice { + filter: invert(60%) sepia(14%) saturate(5479%) hue-rotate(195deg) + brightness(100%) contrast(102%); +} \ No newline at end of file diff --git a/constants/index.ts b/constants/index.ts index 1a97bab4d..a35dccbf9 100644 --- a/constants/index.ts +++ b/constants/index.ts @@ -101,7 +101,8 @@ export const RESPONSE_MESSAGES = { ORGANIZATION_NAME_NOT_PROVIDED_400: { statusCode: 400, message: "'organizationName' not provided." }, INVITE_EXPIRED_400: { statusCode: 400, message: 'Invite expired.' }, INVALID_EMAIL_400: { statusCode: 400, message: 'Invalid email.' }, - USER_OUT_OF_EMAIL_CREDITS_400: { statusCode: 400, message: 'User out of email credits.' } + USER_OUT_OF_EMAIL_CREDITS_400: { statusCode: 400, message: 'User out of email credits.' }, + NO_INVOICE_FOUND_404: { statusCode: 404, message: 'No invoice found.' } } export const SOCKET_MESSAGES = { diff --git a/pages/api/invoices/index.ts b/pages/api/invoices/index.ts new file mode 100644 index 000000000..a63deb1af --- /dev/null +++ b/pages/api/invoices/index.ts @@ -0,0 +1,51 @@ +import { setSession } from 'utils/setSession' +import { RESPONSE_MESSAGES } from 'constants/index' +import { CreateInvoiceParams, UpdateInvoiceParams, createInvoice, updateInvoice } from 'services/invoiceService' + +export default async ( + req: any, + res: any +): Promise => { + await setSession(req, res, true) + const session = req.session + if (req.method === 'POST') { + try { + const createInvoiceParams: CreateInvoiceParams = { + ...req.body, + userId: session.userId + } + const invoice = await createInvoice(createInvoiceParams) + res.status(200).json({ + invoice + }) + } catch (err: any) { + switch (err.message) { + case RESPONSE_MESSAGES.USER_ID_NOT_PROVIDED_400.message: + res.status(400).json(RESPONSE_MESSAGES.USER_ID_NOT_PROVIDED_400) + break + default: + res.status(500).json({ statusCode: 500, message: err.message }) + } + } + } else if (req.method === 'PUT') { + try { + const updateInvoiceParams: UpdateInvoiceParams = { + ...req.body + } + const invoiceId = req.query.invoiceId as string + const invoice = await updateInvoice(session.userId, + invoiceId, updateInvoiceParams) + res.status(200).json({ + invoice + }) + } catch (err: any) { + switch (err.message) { + case RESPONSE_MESSAGES.USER_ID_NOT_PROVIDED_400.message: + res.status(400).json(RESPONSE_MESSAGES.USER_ID_NOT_PROVIDED_400) + break + default: + res.status(500).json({ statusCode: 500, message: err.message }) + } + } + } +} diff --git a/pages/api/invoices/invoiceNumber/index.ts b/pages/api/invoices/invoiceNumber/index.ts new file mode 100644 index 000000000..be2a0056d --- /dev/null +++ b/pages/api/invoices/invoiceNumber/index.ts @@ -0,0 +1,18 @@ +import * as invoiceService from 'services/invoiceService' +import { setSession } from 'utils/setSession' + +export default async ( + req: any, + res: any +): Promise => { + await setSession(req, res) + const userId = req.session.userId + if (req.method === 'GET') { + try { + const invoiceNumber = await invoiceService.getNewInvoiceNumber(userId) + res.status(200).json({ invoiceNumber }) + } catch (err: any) { + res.status(500).json({ statusCode: 500, message: err.message }) + } + } +} diff --git a/pages/api/invoices/transaction/[transactionId].ts b/pages/api/invoices/transaction/[transactionId].ts new file mode 100644 index 000000000..5c115bf63 --- /dev/null +++ b/pages/api/invoices/transaction/[transactionId].ts @@ -0,0 +1,26 @@ +import * as invoiceService from 'services/invoiceService' +import { RESPONSE_MESSAGES } from 'constants/index' +import { setSession } from 'utils/setSession' + +export default async ( + req: any, + res: any +): Promise => { + await setSession(req, res) + const userId = req.session.userId + const transactionId = req.query.transactionId as string + if (req.method === 'GET') { + try { + const invoice = await invoiceService.getInvoiceByTransactionId(transactionId, userId) + res.status(200).json(invoice) + } catch (err: any) { + switch (err.message) { + case RESPONSE_MESSAGES.NO_INVOICE_FOUND_404.message: + res.status(404).json(RESPONSE_MESSAGES.NO_INVOICE_FOUND_404) + break + default: + res.status(500).json({ statusCode: 500, message: err.message }) + } + } + } +} diff --git a/prisma/migrations/20250515150215_invoices/migration.sql b/prisma/migrations/20250515150215_invoices/migration.sql new file mode 100644 index 000000000..d62e6efd2 --- /dev/null +++ b/prisma/migrations/20250515150215_invoices/migration.sql @@ -0,0 +1,25 @@ +-- CreateTable +CREATE TABLE `Invoice` ( + `id` VARCHAR(191) NOT NULL, + `userId` VARCHAR(191) NOT NULL, + `invoiceNumber` VARCHAR(191) NOT NULL, + `transactionId` VARCHAR(191) NULL, + `amount` DECIMAL(65, 30) NOT NULL, + `description` VARCHAR(191) NOT NULL, + `recipientName` VARCHAR(191) NOT NULL, + `recipientAddress` VARCHAR(191) NOT NULL, + `customerName` VARCHAR(191) NOT NULL, + `customerAddress` VARCHAR(191) NOT NULL, + `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updatedAt` DATETIME(3) NOT NULL, + + UNIQUE INDEX `Invoice_invoiceNumber_userId_unique_constraint`(`invoiceNumber`, `userId`), + UNIQUE INDEX `Invoice_transactionId_userId_unique_constraint`(`transactionId`, `userId`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `Invoice` ADD CONSTRAINT `Invoice_transactionId_fkey` FOREIGN KEY (`transactionId`) REFERENCES `Transaction`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `Invoice` ADD CONSTRAINT `Invoice_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `UserProfile`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d102eb0e1..6de5ee27b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -74,6 +74,8 @@ model Transaction { opReturn String @db.LongText @default("") address Address @relation(fields: [addressId], references: [id], onDelete: Cascade, onUpdate: Cascade) prices PricesOnTransactions[] + invoices Invoice[] + createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -166,6 +168,7 @@ model UserProfile { lastSentVerificationEmailAt DateTime? wallets WalletsOnUserProfile[] addresses AddressesOnUserProfiles[] + invoices Invoice[] preferredCurrencyId Int @default(1) preferredTimezone String @db.VarChar(255)@default("") @@ -241,3 +244,25 @@ model OrganizationInvite { organizationId String organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) } + +model Invoice { + id String @id @default(uuid()) + userId String + invoiceNumber String + transactionId String? + transaction Transaction? @relation(fields: [transactionId], references: [id], onUpdate: Cascade, onDelete: Restrict) + userProfile UserProfile @relation(fields: [userId], references: [id], onUpdate: Cascade, onDelete: Restrict) + + amount Decimal + description String + recipientName String + recipientAddress String + customerName String + customerAddress String + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([invoiceNumber, userId], map: "Invoice_invoiceNumber_userId_unique_constraint") + @@unique([transactionId, userId], map: "Invoice_transactionId_userId_unique_constraint") +} \ No newline at end of file diff --git a/services/invoiceService.ts b/services/invoiceService.ts new file mode 100644 index 000000000..1ab5dfc18 --- /dev/null +++ b/services/invoiceService.ts @@ -0,0 +1,107 @@ +import { Decimal } from '@prisma/client/runtime/library' +import prisma from 'prisma/clientInstance' +import { Invoice } from '@prisma/client' +import { RESPONSE_MESSAGES } from 'constants/index' + +export interface CreateInvoiceParams { + userId: string + transactionId?: string + invoiceNumber: string + amount: Decimal + description: string + recipientName: string + recipientAddress: string + customerName: string + customerAddress: string +} + +export interface UpdateInvoiceParams { + description: string + recipientName: string + recipientAddress: string + customerName: string + customerAddress: string +} + +export async function createInvoice (params: CreateInvoiceParams): Promise { + return await prisma.invoice.create({ + data: { + ...params + } + }) +} + +export async function getUserInvoices (userId: string): Promise { + return await prisma.invoice.findMany({ + where: { + userId + } + }) +} + +export async function getInvoiceByTransactionId (transactionId: string, userId: string): Promise { + const invoice = await prisma.invoice.findFirst({ + where: { + transactionId, + userId + } + }) + if (invoice === null) { + throw new Error(RESPONSE_MESSAGES.NO_INVOICE_FOUND_404.message) + } + + return invoice +} + +export async function getInvoiceById (invoiceId: string, userId: string): Promise { + const invoice = await prisma.invoice.findFirst({ + where: { + id: invoiceId, + userId + } + }) + if (invoice === null) { + throw new Error(RESPONSE_MESSAGES.NO_INVOICE_FOUND_404.message) + } + + return invoice +} + +export async function updateInvoice (userId: string, invoiceId: string, params: UpdateInvoiceParams): Promise { + const invoice = await getInvoiceById(invoiceId, userId) + if (invoice === null) { + throw new Error(RESPONSE_MESSAGES.NO_INVOICE_FOUND_404.message) + } + + return await prisma.invoice.update({ + where: { + id: invoice.id + }, + data: params + }) +} + +export async function getNewInvoiceNumber (userId: string): Promise { + const year = new Date().getFullYear() + const defaultPattern = /^\d{4}-\d+$/ + + const userInvoices = await prisma.invoice.findMany({ + where: { + userId + }, + orderBy: { + invoiceNumber: 'desc' + } + }) + const invoicesWithOurPattern = userInvoices.filter(invoice => defaultPattern.test(invoice.invoiceNumber)) + + if (invoicesWithOurPattern === null || invoicesWithOurPattern.length < userInvoices.length) { + return + } + + const invoiceWithTheLatestInvoiceNumber = invoicesWithOurPattern[0] + const nextInvoiceNumber = (invoiceWithTheLatestInvoiceNumber != null) ? parseInt(invoiceWithTheLatestInvoiceNumber.invoiceNumber.split('-')[1]) + 1 : 1 + + const invoiceNumber = `${year}-${String(nextInvoiceNumber)}` + return invoiceNumber +} diff --git a/services/transactionService.ts b/services/transactionService.ts index aa910e0c3..87b7dcc10 100644 --- a/services/transactionService.ts +++ b/services/transactionService.ts @@ -114,6 +114,14 @@ const includePaybuttonsAndPrices = { }, ...includePrices } +export const includePaybuttonsAndPricesAndInvoices = { + ...includePaybuttonsAndPrices, + invoices: true +} +const transactionWithAddressAndPricesAndInvoices = Prisma.validator()( + { include: includePaybuttonsAndPricesAndInvoices } +) +export type TransactionWithAddressAndPricesAndInvoices = Prisma.TransactionGetPayload const transactionsWithPaybuttonsAndPrices = Prisma.validator()( { @@ -151,11 +159,10 @@ export async function fetchTransactionsByAddressListWithPagination ( pageSize: number, orderBy?: string, orderDesc = true, - networkIdsListFilter?: number[], + networkIdsListFilter?: number[] ): Promise { - const orderDescString: Prisma.SortOrder = orderDesc ? 'desc' : 'asc' - + // Get query for orderBy that works with nested properties (e.g. `address.networkId`) let orderByQuery if (orderBy !== undefined && orderBy !== '') { @@ -189,10 +196,10 @@ export async function fetchTransactionsByAddressListWithPagination ( } } }, - include: includePaybuttonsAndPrices, + include: includePaybuttonsAndPricesAndInvoices, orderBy: orderByQuery, skip: page * pageSize, - take: pageSize, + take: pageSize }) } @@ -578,7 +585,7 @@ export async function fetchTransactionsByPaybuttonIdWithPagination ( pageSize, orderBy, orderDesc, - networkIds); + networkIds) if (transactions.length === 0) { throw new Error(RESPONSE_MESSAGES.NO_TRANSACTION_FOUND_404.message) diff --git a/utils/validators.ts b/utils/validators.ts index 27f20eb0e..8d6f18348 100644 --- a/utils/validators.ts +++ b/utils/validators.ts @@ -550,3 +550,13 @@ export const parseUpdateUserTimezonePUTRequest = function (params: UpdateUserTim return { timezone: params.timezone } } + +export interface CreateInvoicePOSTParameters { + transactionId: string + amount: Prisma.Decimal + description: string + recipientName: string + recipientAddress: string + customerName: string + customerAddress: string +} diff --git a/yarn.lock b/yarn.lock index d4ddc8cbe..0a9587cef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5420,6 +5420,11 @@ lru-cache@^7.14.1: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.1.tgz" integrity sha512-8/HcIENyQnfUTCDizRu9rrDyG6XG/21M4X7/YEGZeD76ZJilFPAUVb/2zysFf7VVO1LEjCDFyHp8pMMvozIrvg== +lucide-react@^0.510.0: + version "0.510.0" + resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.510.0.tgz#933b19d893b30ac5cb355e4b91eeefc13088caa3" + integrity sha512-p8SQRAMVh7NhsAIETokSqDrc5CHnDLbV29mMnzaXx+Vc/hnqQzwI2r0FMWCcoTXnbw2KEjy48xwpGdEL+ck06Q== + luxon@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz"