Skip to content
5 changes: 3 additions & 2 deletions app/models/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ export const TRANSACTION_STATUS = {
SUBMITTED: 'SUBMITTED',
VERIFIED: 'VERIFIED',
REJECTED: 'REJECTED',
}
} as const

export type TransactionStatus = 'SUBMITTED' | 'VERIFIED' | 'REJECTED'
export type TransactionStatus =
typeof TRANSACTION_STATUS[keyof typeof TRANSACTION_STATUS]

export const TRANSACTION_METHOD = {
BANK_TRANSFER: 'BANK_TRANSFER',
Expand Down
21 changes: 21 additions & 0 deletions app/routes/dashboard.transactions.$transactionId.$action.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
TransactionWithUser,
updateTransactionStatus,
} from '~/models/transaction'
import { canUpdateTransactionStatus } from '~/utils/transaction-status'
import { printLocaleDateTimeString, printRupiah } from '~/utils/format'
import { TransactionStatus, TRANSACTION_STATUS } from '~/models/enum'
import { requireUser } from '~/services/auth.server'
Expand Down Expand Up @@ -75,6 +76,26 @@ export const action: ActionFunction = async ({ request, params }) => {
}
}

Comment thread
zainfathoni marked this conversation as resolved.
const existingTransaction = await getTransactionById(transactionId)
if (!existingTransaction) {
return redirect('/dashboard/transactions')
}

if (
!canUpdateTransactionStatus(
existingTransaction.status as TransactionStatus,
status as TransactionStatus
Comment thread
zainfathoni marked this conversation as resolved.
)
) {
throw json(
{
formError:
'Transaksi yang sudah diverifikasi tidak dapat diubah menjadi ditolak.',
},
Comment thread
zainfathoni marked this conversation as resolved.
Outdated
{ status: 400 }
)
}

const transaction = await updateTransactionStatus(
Comment thread
zainfathoni marked this conversation as resolved.
transactionId,
notes,
Expand Down
6 changes: 4 additions & 2 deletions app/routes/dashboard.transactions.$transactionId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,13 @@ export default function TransactionDetailsPage() {
<SingleColumnLayout>
<div className="min-h-full">
<TransactionDetails transaction={transaction} user={transaction.user}>
{/* TODO: Disable rejecting a verified transaction */}
<TertiaryButtonLink
to={`reject?${searchParams.toString()}`}
replace
disabled={transaction.status === TRANSACTION_STATUS.REJECTED}
disabled={
transaction.status === TRANSACTION_STATUS.REJECTED ||
transaction.status === TRANSACTION_STATUS.VERIFIED
}
>
Tolak Transaksi
</TertiaryButtonLink>
Expand Down
18 changes: 17 additions & 1 deletion app/services/__tests__/email.server.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { User } from '@prisma/client'
import { sendEmail } from '../email.server'
import * as emailProvider from '~/services/email-provider.server'
import { userBuilder } from '~/models/__mocks__/user'
Comment thread
zainfathoni marked this conversation as resolved.

describe('sendEmail', () => {
const user = userBuilder() as User

beforeEach(() => {
vi.restoreAllMocks()
})

it('call emailProvider.sendEmail method', async () => {
const spy = vi.spyOn(emailProvider, 'sendEmail')

await sendEmail({
emailAddress: user.email,
magicLink: 'http://localhost:3000/magic',
Expand All @@ -14,6 +22,14 @@ describe('sendEmail', () => {
form: new FormData(),
})

// TODO: assert emailProvider.sendEmail was called
expect(spy).toHaveBeenCalledOnce()
expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
to: user.email,
from: expect.any(String),
subject: expect.any(String),
html: expect.stringContaining('localhost:3000/magic'),
})
)
})
})
58 changes: 58 additions & 0 deletions app/utils/__tests__/transaction-status.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { canUpdateTransactionStatus } from '../transaction-status'
import { TRANSACTION_STATUS } from '~/models/enum'

describe('canUpdateTransactionStatus', () => {
it('allows submitted to verified', () => {
expect(
canUpdateTransactionStatus(
TRANSACTION_STATUS.SUBMITTED,
TRANSACTION_STATUS.VERIFIED
)
).toBe(true)
})

it('allows submitted to rejected', () => {
expect(
canUpdateTransactionStatus(
TRANSACTION_STATUS.SUBMITTED,
TRANSACTION_STATUS.REJECTED
)
).toBe(true)
})

it('prevents verified from being rejected', () => {
expect(
canUpdateTransactionStatus(
TRANSACTION_STATUS.VERIFIED,
TRANSACTION_STATUS.REJECTED
)
).toBe(false)
})

it('allows verified to remain verified', () => {
expect(
canUpdateTransactionStatus(
TRANSACTION_STATUS.VERIFIED,
TRANSACTION_STATUS.VERIFIED
)
).toBe(true)
})

it('allows rejected to be re-verified', () => {
expect(
canUpdateTransactionStatus(
TRANSACTION_STATUS.REJECTED,
TRANSACTION_STATUS.VERIFIED
)
).toBe(true)
})

it('allows rejected to remain rejected', () => {
expect(
canUpdateTransactionStatus(
TRANSACTION_STATUS.REJECTED,
TRANSACTION_STATUS.REJECTED
)
).toBe(true)
})
})
15 changes: 15 additions & 0 deletions app/utils/transaction-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { TransactionStatus, TRANSACTION_STATUS } from '~/models/enum'

export function canUpdateTransactionStatus(
currentStatus: TransactionStatus,
destinationStatus: TransactionStatus
) {
if (
currentStatus === TRANSACTION_STATUS.VERIFIED &&
destinationStatus === TRANSACTION_STATUS.REJECTED
) {
return false
}

return true
}
8 changes: 6 additions & 2 deletions e2e/profile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
})

// Skip profile tests on staging - member user state refreshed from production
test.skip(

Check warning on line 9 in e2e/profile.spec.ts

View workflow job for this annotation

GitHub Actions / ESLint

Unexpected use of the `.skip()` annotation
isStagingEnv,
'Skipping on staging - requires stable member-edit fixture'
)
Expand All @@ -30,16 +30,18 @@
name: /nomor whatsapp/i,
})

// Fill phoneNumber with invalid value
// Click first to ensure onFocus fires reliably on WebKit (iPhone 11),
// then fill with invalid value and blur to trigger client-side validation.
await phoneNumber.click()
await phoneNumber.fill('6512345678')
await phoneNumber.blur()

// Expect visibility only when JavaScript is enabled
if (!noscript) {

Check warning on line 40 in e2e/profile.spec.ts

View workflow job for this annotation

GitHub Actions / ESLint

Avoid having conditionals in tests
const errorMessage = page.getByText(
'Nomor WhatsApp harus mengandung kode negara dan nomor telepon'
)
await expect(errorMessage).toBeVisible({ timeout: 10000 })

Check warning on line 44 in e2e/profile.spec.ts

View workflow job for this annotation

GitHub Actions / ESLint

Avoid calling `expect` conditionally`
}

// Fill phoneNumber with valid value
Expand All @@ -64,13 +66,15 @@
name: /nama lengkap/i,
})

// Clear name and trigger validation
// Click first to ensure onFocus fires reliably on WebKit (iPhone 11),
// then clear and blur to trigger client-side validation.
await name.click()
await name.fill('')
await name.blur()

if (!noscript) {

Check warning on line 75 in e2e/profile.spec.ts

View workflow job for this annotation

GitHub Actions / ESLint

Avoid having conditionals in tests
const errorMessage = page.getByText('Nama Lengkap wajib diisi')
await expect(errorMessage).toBeVisible({ timeout: 10000 })

Check warning on line 77 in e2e/profile.spec.ts

View workflow job for this annotation

GitHub Actions / ESLint

Avoid calling `expect` conditionally`
}

// Fill valid name and submit
Expand Down
Loading