Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: edit transaction #5

Merged
merged 2 commits into from
Jul 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions __tests__/functions/calculateMonthlyReportData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Monthly Salary',
amount: '1000',
isIncome: true,
isEdited: false,
balance: '1000',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -39,6 +40,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Weekly groceries',
amount: '200',
isIncome: false,
isEdited: false,
balance: '800',
currency: {
name: CURRENCY_NAME.UAH,
Expand Down Expand Up @@ -66,6 +68,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Groceries',
amount: '200',
isIncome: false,
isEdited: false,
balance: '800',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -83,6 +86,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Bus fare',
amount: '100',
isIncome: false,
isEdited: false,
balance: '700',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -100,6 +104,7 @@ describe('calculateMonthlyReportData', () => {
description: 'More groceries',
amount: '150',
isIncome: false,
isEdited: false,
balance: '550',
currency: {
name: CURRENCY_NAME.UAH,
Expand Down Expand Up @@ -130,6 +135,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Monthly Salary',
amount: '1000',
isIncome: true,
isEdited: false,
balance: '1000',
currency: {
name: CURRENCY_NAME.UAH,
Expand Down Expand Up @@ -160,6 +166,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Groceries',
amount: '200',
isIncome: false,
isEdited: false,
balance: '800',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -186,6 +193,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Monthly Salary',
amount: '1000',
isIncome: true,
isEdited: false,
balance: '1000',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -206,6 +214,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Groceries',
amount: '200',
isIncome: false,
isEdited: false,
balance: '800',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -232,6 +241,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Monthly Salary',
amount: '1000',
isIncome: true,
isEdited: false,
balance: '1000',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -252,6 +262,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Groceries',
amount: '200',
isIncome: false,
isEdited: false,
balance: '800',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -278,6 +289,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Monthly Salary',
amount: '1000',
isIncome: true,
isEdited: false,
balance: '1000',
currency: {
name: CURRENCY_NAME.UAH,
Expand All @@ -298,6 +310,7 @@ describe('calculateMonthlyReportData', () => {
description: 'Groceries',
amount: '200',
isIncome: false,
isEdited: false,
balance: '800',
currency: {
name: CURRENCY_NAME.UAH,
Expand Down
99 changes: 79 additions & 20 deletions app/lib/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ export async function createTransaction(
try {
const newTransaction: Omit<
TTransaction,
'createdAt' | 'updatedAt' | 'transactionLimit'
'createdAt' | 'updatedAt' | 'transactionLimit' | 'isEdited'
> = {
id: crypto.randomUUID(),
userId,
description: capitalizeFirstLetter(
formData.get('description') as TTransaction['description'],
),
).trim(),
amount: formData.get('amount') as TTransaction['amount'],
category: getCategoryWithEmoji(
formData.get('category'),
Expand Down Expand Up @@ -310,24 +310,58 @@ export async function getAllTransactions(
}
export const getCachedAllTransactions = cache(getAllTransactions)

// export async function editTransaction(
// id: TTransaction['id'],
// updates: Partial<TTransaction>,
// ): Promise<void> {
// if (!id) {
// throw new Error('Transaction ID is required to edit a transaction.')
// }
// if (!updates || Object.keys(updates).length === 0) {
// throw new Error('No updates provided.')
// }
// try {
// await dbConnect()
// await TransactionModel.updateOne({ id }, { $set: updates })
// revalidatePath(ROUTE.HOME)
// } catch (err) {
// throw err
// }
// }
export async function editTransactionById(
id: TTransaction['id'],
newTransactionData: Partial<TTransaction>,
): Promise<void> {
if (!id) {
throw new Error('Transaction ID is required to update a transaction.')
}
try {
await dbConnect()
const session = await TransactionModel.startSession()
try {
session.startTransaction()
const updateFields: Partial<TTransaction> = {}
if (newTransactionData.description) {
updateFields.description = newTransactionData.description.trim()
}
if (newTransactionData.amount) {
updateFields.amount = newTransactionData.amount.replace(/\s/g, '')
const amount = parseFloat(updateFields.amount)
let balance = 0
if (newTransactionData.isIncome) {
balance += amount
} else {
balance -= amount
}
updateFields.balance = balance.toString()
}
if (newTransactionData.category) {
updateFields.category = newTransactionData.category
}
if (newTransactionData.currency) {
updateFields.currency = newTransactionData.currency
}
if (newTransactionData.isIncome !== undefined) {
updateFields.isIncome = newTransactionData.isIncome
}
if (newTransactionData.isEdited) {
updateFields.isEdited = newTransactionData.isEdited
}
await TransactionModel.updateOne({ id }, updateFields, { session })
await session.commitTransaction()
session.endSession()
revalidatePath(ROUTE.HOME)
} catch (err) {
await session.abortTransaction()
session.endSession()
throw err
}
} catch (err) {
throw err
}
}

export async function deleteTransaction(id: TTransaction['id']): Promise<void> {
if (!id) {
Expand All @@ -342,6 +376,31 @@ export async function deleteTransaction(id: TTransaction['id']): Promise<void> {
}
}

export async function findTransactionById(
id: TTransaction['id'],
): Promise<TTransaction | null> {
if (!id) {
throw new Error(
'Transaction ID and User ID are required to find a transaction.',
)
}
try {
await dbConnect()
const transaction = await TransactionModel.findOne({
id,
}).lean<TTransaction>({
transform: (doc: TRawTransaction) => {
delete doc._id
delete doc.__v
return doc
},
})
return transaction
} catch (err) {
throw err
}
}

export async function deleteAllTransactionsAndSignOut(
userId: TUserId,
): Promise<void> {
Expand Down
8 changes: 5 additions & 3 deletions app/lib/models/transaction.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,15 @@ const transactionSchema = new Schema<TTransaction>(
type: Number,
default: null,
},
isEdited: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
},
)

transactionSchema.plugin(mongooseEncryptionDecryption, {
).plugin(mongooseEncryptionDecryption, {
encodedFields: ['amount', 'balance'],
privateKey: process.env.ENCRYPTION_SECRET,
})
Expand Down
1 change: 1 addition & 0 deletions app/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type TTransaction = {
}
| undefined
transactionLimit: number | null | undefined
isEdited: boolean
createdAt: Date
updatedAt: Date
}
Expand Down
4 changes: 4 additions & 0 deletions app/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export const getEmojiFromCategory = (category: TTransaction['category']) => {
return category.slice(0, 2)
}

export const getCategoryWithoutEmoji = (category: TTransaction['category']) => {
return category.slice(2).trim()
}

export const getSlicedCurrencyCode = (code: CURRENCY_CODE) => {
return code.toLocaleLowerCase().slice(0, 2)
}
Expand Down
2 changes: 1 addition & 1 deletion app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function NotFound() {
return (
<main className='mx-auto flex h-screen max-w-md flex-col items-center justify-center gap-4 px-4 text-center xs:px-0'>
<div className='flex flex-col items-center'>
<p className='text-default-300'>#404Error</p>
<p className='text-default-300'>#404NotFound</p>
</div>
<h1 className='text-lg font-medium'>
Looks like this page got lost in the internet abyss. Do not worry, we
Expand Down
1 change: 1 addition & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export default async function Home({
amount={transaction.amount}
currency={transaction.currency}
isIncome={transaction.isIncome}
isEdited={transaction.isEdited}
createdAt={transaction.createdAt}
/>
</li>
Expand Down
27 changes: 27 additions & 0 deletions app/transaction/[id]/edit/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Link from 'next/link'

import { ROUTE } from '@/config/constants/routes'

import ClientButton from '@/app/ui/default-button'
import WithSidebar from '@/app/ui/sidebar/with-sidebar'

export default function NotFound() {
const content = (
<main className='mx-auto flex h-screen max-w-md flex-col items-center justify-center gap-4 px-4 text-center xs:px-0'>
<div className='flex flex-col items-center'>
<p className='text-default-300'>#404NotFound</p>
</div>
<h1 className='text-lg font-medium'>
Could not find the requested transaction.
</h1>
<Link href={ROUTE.HOME}>
<ClientButton
title='Return home'
className='max-w-md bg-primary font-medium text-default-50'
/>
</Link>
</main>
)

return <WithSidebar contentNearby={content} />
}
31 changes: 31 additions & 0 deletions app/transaction/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Metadata } from 'next'
import { notFound } from 'next/navigation'

import { findTransactionById } from '@/app/lib/actions'

import WithSidebar from '@/app/ui/sidebar/with-sidebar'
import TransactionFormEdit from '@/app/ui/transaction-form-edit'

const PAGE_TITLE = 'Edit Transaction'

export const metadata: Metadata = {
title: PAGE_TITLE,
}

export default async function Page({ params }: { params: { id: string } }) {
const { id } = params
const transaction = await findTransactionById(id)

if (!transaction) {
notFound()
}

const content = (
<main className='mx-auto max-w-2xl'>
<h1 className='mb-8 text-center text-2xl font-bold'>{PAGE_TITLE}</h1>
<TransactionFormEdit transaction={transaction} />
</main>
)

return <WithSidebar contentNearby={content} />
}
2 changes: 1 addition & 1 deletion app/ui/settings/currency.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function Currency({ userId, currency }: TProps) {
await updateCurrency(userId, getCurrencyData(key as CURRENCY_NAME))
toast.success('Currency updated.')
} catch (err) {
toast.error('Error updating currency.')
toast.error('Failed to update currency.')
throw err
} finally {
setIsLoading(false)
Expand Down
2 changes: 1 addition & 1 deletion app/ui/settings/transaction-limit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ function TransactionLimit({
await updateTransactionLimit(userId, Number(key))
toast.success('Transaction limit updated.')
} catch (err) {
toast.error('Error updating transaction limit.')
toast.error('Failed to update transaction limit.')
throw err
} finally {
setIsLoading(false)
Expand Down
Loading