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: transaction confirmation and verification flow #127

Merged
merged 34 commits into from
Feb 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fba901e
feat: render different icons for different transaction status
zainfathoni Feb 26, 2022
e938cc6
feat: style Kontak WhatsApp button and simplify the e2e tests to be f…
zainfathoni Feb 26, 2022
5998b60
feat: open verification dialog
zainfathoni Feb 26, 2022
a009f97
feat: verify and reject transactions
zainfathoni Feb 26, 2022
5245b12
feat: increase the modal's max width
zainfathoni Feb 26, 2022
9e7cb25
feat: add tabs
zainfathoni Feb 27, 2022
6dd65c4
feat: increase the sidebar breakpoint into lg
zainfathoni Feb 27, 2022
535119a
feat: transactions navigation
zainfathoni Feb 27, 2022
3d29209
feat: replace user initials with a user icon
zainfathoni Feb 27, 2022
ae62f4c
feat: order transaction query
zainfathoni Feb 27, 2022
362b8e9
feat: transactions pagination
zainfathoni Feb 27, 2022
b203749
feat: update email template
zainfathoni Feb 27, 2022
169dbfa
feat: redirect to verify page if a transaction exists
zainfathoni Feb 27, 2022
d4935b1
feat: extract TransactionDetails to be reusable for members and authors
zainfathoni Feb 27, 2022
0956d98
refactor: extract ButtonLink component
zainfathoni Feb 27, 2022
622fd05
test: fix e2e test for CTA by adding another fixture
zainfathoni Feb 27, 2022
8d49dd7
test: refactor e2e tests fixtures setup
zainfathoni Feb 27, 2022
b7a46ac
fix: render transaction's user instead of the currently logged in user
zainfathoni Feb 27, 2022
7ef5f7f
fix: move nobody.json to the correct directory
zainfathoni Feb 27, 2022
6b50580
refactor: pass the user manually to TransactionDetails for flexibility
zainfathoni Feb 27, 2022
a80adef
feat: verification navigation
zainfathoni Feb 27, 2022
0b1c2ee
feat: remove irrelevant data from the profile page
zainfathoni Feb 27, 2022
7be1fb7
feat: remove irrelevant tabs from the edit profile page
zainfathoni Feb 27, 2022
7bc67fb
feat: update sidebar navigation copy to Bahasa Indonesia
zainfathoni Feb 27, 2022
040342b
feat: update copy
zainfathoni Feb 27, 2022
0966d2b
feat: style payment confirmation form
zainfathoni Feb 27, 2022
6008f5c
feat: add a secondary button to edit transaction details
zainfathoni Feb 27, 2022
a67db43
feat: implement completed page
zainfathoni Feb 27, 2022
6c0b534
refactor: reuse Pricing component in the Dashboard home page
zainfathoni Feb 27, 2022
0410b37
feat: update subscription according to the transaction changes
zainfathoni Feb 27, 2022
1e74509
test: fix auth-error.spec.ts failures in local
zainfathoni Feb 27, 2022
4ee0224
refactor: compose WhatsApp link using URLSearchParams()
zainfathoni Feb 27, 2022
c1095a6
fix: allow admin to access everything
zainfathoni Feb 27, 2022
49040c3
Revert "test: fix auth-error.spec.ts failures in local"
zainfathoni Feb 27, 2022
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
32 changes: 23 additions & 9 deletions app/components/__tests__/transaction-item.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { build, fake } from '@jackfranklin/test-data-bot'
import { build, fake, oneOf } from '@jackfranklin/test-data-bot'
import { render, screen } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { TransactionItem, TransactionItemProps } from '../transaction-item'
import { printLocaleDateTimeString } from '~/utils/format'
import { TRANSACTION_STATUS } from '~/models/enum'

const transactionItemBuilder = build<TransactionItemProps>('TransactionItem', {
fields: {
Expand All @@ -11,37 +12,50 @@ const transactionItemBuilder = build<TransactionItemProps>('TransactionItem', {
bankAccountNumber: fake((f) => f.finance.account()),
bankName: fake((f) => f.company.companyName()),
dateTime: fake((f) => f.date.past()),
status: oneOf(TRANSACTION_STATUS),
},
})
describe('TransactionItem', () => {
it('should display customer name correctly', () => {
const props = transactionItemBuilder()
it('should display bankAccountName correctly', () => {
const props = transactionItemBuilder({
overrides: {
status: TRANSACTION_STATUS.VERIFIED,
},
})

render(<TransactionItem {...props} />, { wrapper: MemoryRouter })

expect(screen.getByLabelText(/nama rekening/i)).toHaveTextContent(
props.bankAccountName
)
})
it('should display customer email correctly', () => {
const props = transactionItemBuilder()
it('should display bankName correctly', () => {
const props = transactionItemBuilder({
overrides: {
status: TRANSACTION_STATUS.REJECTED,
},
})

render(<TransactionItem {...props} />, { wrapper: MemoryRouter })

expect(screen.getByLabelText(/nama bank/i)).toHaveTextContent(
props.bankName
)
})
it('should display transaction datetime correctly', () => {
const props = transactionItemBuilder()
it('should display datetime correctly', () => {
const props = transactionItemBuilder({
overrides: {
status: TRANSACTION_STATUS.SUBMITTED,
},
})

render(<TransactionItem {...props} />, { wrapper: MemoryRouter })

expect(screen.getByLabelText(/waktu transaksi/i)).toHaveTextContent(
printLocaleDateTimeString(props.dateTime ?? '')
)
})
it('should display transaction bank name correctly', () => {
it('should display bankAccountNumber correctly', () => {
const props = transactionItemBuilder()

render(<TransactionItem {...props} />, { wrapper: MemoryRouter })
Expand All @@ -50,7 +64,7 @@ describe('TransactionItem', () => {
props.bankAccountNumber
)
})
it('should render transaction details link correctly', () => {
it('should link to transaction details page correctly', () => {
const props = transactionItemBuilder()

render(<TransactionItem {...props} />, { wrapper: MemoryRouter })
Expand Down
85 changes: 85 additions & 0 deletions app/components/button-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ReactNode } from 'react'
import { Link } from 'remix'
import { classNames } from '~/utils/class-names'

const buttonClassNames =
'disabled:opacity-80 disabled:bg-gray-100 disabled:text-gray-500 disabled:hover:cursor-not-allowed text-center inline-flex items-center justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blue-500'

function DisabledButton({
children,
className,
}: {
children: ReactNode
className?: string
}) {
return (
<button disabled className={classNames(buttonClassNames, className ?? '')}>
{children}
</button>
)
}

type ButtonLinkProps = {
to: string
replace?: boolean
children: ReactNode
external?: boolean
disabled?: boolean
className?: string
}

export function ButtonLink({
to,
replace,
children,
external,
disabled,
className,
...props
}: ButtonLinkProps) {
return disabled ? (
<DisabledButton>{children}</DisabledButton>
) : external ? (
<a
href={to}
target="_blank"
rel="noopener noreferrer"
className={classNames(buttonClassNames, className ?? '')}
>
{children}
</a>
) : (
<Link
to={to}
replace={replace}
className={classNames(
buttonClassNames,
'w-full sm:w-auto',
className ?? ''
)}
{...props}
>
{children}
</Link>
)
}

export function PrimaryButtonLink(props: ButtonLinkProps) {
return (
<ButtonLink
className="border-transparent text-white bg-blue-600 hover:bg-blue-700 focus:ring-red-500"
{...props}
/>
)
}
export function SecondaryButtonLink(props: ButtonLinkProps) {
return <ButtonLink className="text-gray-700" {...props} />
}
export function TertiaryButtonLink(props: ButtonLinkProps) {
return (
<ButtonLink
className="border-transparent text-white bg-red-600 hover:bg-red-700 focus:ring-red-500 mr-auto"
{...props}
/>
)
}
4 changes: 2 additions & 2 deletions app/components/form-elements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type InputProps = (
| JSX.IntrinsicElements['input']
) & {
status?: InputStatus
type?: 'textarea'
type?: 'textarea' | 'datetime-local' | 'date' | 'time' | 'number'
className?: string
}

Expand All @@ -52,7 +52,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
{...(props as JSX.IntrinsicElements['input'])}
className={className}
ref={ref}
type="text"
type={props.type ?? 'text'}
/>
{props.status === 'error' ? (
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
Expand Down
36 changes: 36 additions & 0 deletions app/components/page-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ReactNode } from 'react'
import { Link } from 'remix'
import { classNames } from '~/utils/class-names'

export function PageLink({
to,
isCurrentPage,
disableOnCurrentPage,
children,
}: {
to: string
isCurrentPage: boolean
disableOnCurrentPage?: boolean
children: ReactNode
}) {
const className = classNames(
'border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium',
isCurrentPage
? 'border-purple-500 text-purple-600 disabled:cursor-not-allowed'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-200',
disableOnCurrentPage ? 'disabled:text-gray-400 disabled:border-hidden' : ''
)
if (isCurrentPage) {
return (
<button disabled className={className}>
{children}
</button>
)
} else {
return (
<Link to={to} className={className}>
{children}
</Link>
)
}
}
22 changes: 16 additions & 6 deletions app/components/sections/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ type PricingProps = {
title: string
description: React.ReactNode
children: React.ReactNode
signupLink?: string
isSubscribed?: boolean
}
export const Pricing = ({
title,
description,
children,
signupLink = '/login',
isSubscribed = false,
}: PricingProps): JSX.Element => {
return (
<div className="bg-gray-100" id="biaya">
Expand Down Expand Up @@ -113,12 +117,18 @@ export const Pricing = ({
</p>
<div className="mt-6">
<div className="rounded-md shadow">
<Link
to="/login"
className="flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-white bg-gray-800 hover:bg-gray-900"
>
Daftarkan Diri
</Link>
{isSubscribed ? (
<button className="flex items-center justify-center px-5 py-3 border border-transparent text-base w-full font-medium rounded-md text-gray-400 bg-gray-200 cursor-not-allowed">
Sudah terdaftar
</button>
) : (
<Link
to={signupLink}
className="flex items-center justify-center px-5 py-3 border border-transparent text-base font-medium rounded-md text-white bg-gray-800 hover:bg-gray-900"
>
Daftarkan Diri
</Link>
)}
</div>
</div>
</div>
Expand Down
Loading