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: Whatsapp Link #83

Merged
merged 37 commits into from
Feb 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7193352
feat: added confirm remix outlet component
AchmadWahyu Dec 30, 2021
03d7c92
Merge branch 'main' of https://github.com/AchmadWahyu/kelas.rumahberb…
AchmadWahyu Dec 31, 2021
bf09fc8
refactor: move contents from confirm component to component
AchmadWahyu Dec 31, 2021
16dae95
feat: added remix's outlet component to render component
AchmadWahyu Dec 31, 2021
3a68f42
Merge branch 'main' into feature/79-whatsapp-link
Dec 31, 2021
d53a935
Merge branch 'main' of https://github.com/AchmadWahyu/kelas.rumahberb…
AchmadWahyu Dec 31, 2021
043a1ff
feat: write transaction information into database upon form submission
AchmadWahyu Jan 1, 2022
60fe7a0
Merge branch 'feature/79-whatsapp-link' of https://github.com/AchmadW…
AchmadWahyu Jan 1, 2022
066f679
Merge branch 'main' into feature/79-whatsapp-link
zainfathoni Jan 6, 2022
283a8f6
feat: compose whatsapp message
AchmadWahyu Jan 7, 2022
ef048ae
fix: update transaction fields
AchmadWahyu Jan 8, 2022
6824a2c
Merge branch 'main' of https://github.com/AchmadWahyu/kelas.rumahberb…
AchmadWahyu Jan 8, 2022
e15b3c7
feat: added getFirstCourse
AchmadWahyu Jan 8, 2022
0c3d6ee
fix: remove formattedPhoneNumber for authorPhoneNumber
AchmadWahyu Jan 8, 2022
46847b5
feat: added getFirstCourse and debugging action onSubmit
AchmadWahyu Jan 8, 2022
78e66cd
Merge branch 'main' of https://github.com/AchmadWahyu/kelas.rumahberb…
AchmadWahyu Jan 8, 2022
d7f4dc1
Merge branch 'main' into feature/79-whatsapp-link
zainfathoni Jan 9, 2022
4da8947
fix: added asertion to amount and paymentTime
AchmadWahyu Jan 9, 2022
d75f808
feat: set not found error case redirect to dashboard
AchmadWahyu Jan 9, 2022
19084c4
refactor: remove unused type definition and change page name
AchmadWahyu Jan 9, 2022
818f41c
style: remove unused imports
AchmadWahyu Jan 9, 2022
7c9a70a
Merge branch 'feature/79-whatsapp-link' of https://github.com/AchmadW…
AchmadWahyu Jan 9, 2022
eaa9da2
test: decrease code coverage threshold
zainfathoni Jan 9, 2022
7d1e0e2
test: remove routes from code coverage collection for now
zainfathoni Jan 10, 2022
a42c982
Merge branch 'main' into feature/79-whatsapp-link
zainfathoni Feb 25, 2022
b9a6dbf
Merge branch 'main' into pr/AchmadWahyu/83
zainfathoni Feb 25, 2022
bb894a7
feat: add a CTA button in the Purchase index page
zainfathoni Feb 25, 2022
64a377a
fix: strip leading plus from the phone number to comply with WhatsApp
zainfathoni Feb 26, 2022
c51b1cc
feat: move the confirmation dialog into the /verify pathname
zainfathoni Feb 26, 2022
48927d8
refactor: rename the default components
zainfathoni Feb 26, 2022
da22235
feat: load first transaction if exists
zainfathoni Feb 26, 2022
f1ba1ac
fix: use array index instead of .at() due to WebKit limited support
zainfathoni Feb 26, 2022
9de0a63
fix: handle a case where there is no prior transaction
zainfathoni Feb 26, 2022
ed3ddf5
feat: add a CatchBoundary
zainfathoni Feb 26, 2022
1dd7aeb
fix: formatDateTime
zainfathoni Feb 26, 2022
814ad45
Revert "test: remove routes from code coverage collection for now"
zainfathoni Jan 10, 2022
c9941ef
docs: add TODO item for styling the confirmation form
zainfathoni Feb 26, 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
6 changes: 6 additions & 0 deletions app/models/course.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { db } from '~/utils/db.server'

export async function getFirstCourse() {
// TODO: refactor this code once the assumption that there is only one course is no longer valid
return await db.course.findFirst()
}
17 changes: 17 additions & 0 deletions app/models/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import { getFirstCourse } from './course'
import { db } from '~/utils/db.server'

export async function getFirstTransaction(userId: string) {
// TODO: refactor this code once the assumption that there is only one transaction for each user and course is no longer valid
const course = await getFirstCourse()

if (!course) {
return null
}

return await db.transaction.findFirst({
where: {
userId,
courseId: course.id,
},
})
}

export async function getTransactionDetails(id: string) {
return await db.transaction.findUnique({
where: { id },
Expand Down
2 changes: 1 addition & 1 deletion app/routes/dashboard/purchase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const loader: LoaderFunction = async ({ request }) => {
}

export default function Purchase() {
const { pathname } = useMatches()?.at(-1) ?? {}
const { pathname } = useMatches()?.[3] ?? {}
const currentStepIdx = STEPS.findIndex((step) => step.pathname === pathname)

return (
Expand Down
2 changes: 1 addition & 1 deletion app/routes/dashboard/purchase/completed.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default function Confirm() {
export default function Completed() {
return (
<div className="border-4 border-dashed border-gray-200 rounded-lg h-96" />
)
Expand Down
248 changes: 246 additions & 2 deletions app/routes/dashboard/purchase/confirm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,249 @@
export default function Confirm() {
import { Form, json, Link, redirect, useCatch, useLoaderData } from 'remix'
import type { ActionFunction, LoaderFunction, ThrownResponse } from 'remix'
import { Transaction, User } from '@prisma/client'
import { XCircleIcon } from '@heroicons/react/solid'
import { validateRequired } from '~/utils/validators'
import { auth } from '~/services/auth.server'
import { db } from '~/utils/db.server'
import { getFirstCourse } from '~/models/course'
import { getFirstTransaction } from '~/models/transaction'
import { getUser } from '~/models/user'
import { formatDateTime } from '~/utils/format'

interface TransactionFields {
userId: string
courseId: string
authorId: string
bankName: string
bankAccountNumber: string
bankAccountName: string
amount: number
datetime: Date
status: string
}

export const loader: LoaderFunction = async ({ request }) => {
const { id } = await auth.isAuthenticated(request, {
failureRedirect: '/login',
})

// TODO: we can use the user instance from the auth service only when we always commit new changes in /profile/edit
// until then, we need to fetch the user from the database
const user = await getUser(id)

if (!user) {
redirect('/logout')
}

const transaction = await getFirstTransaction(id)

if (!transaction) {
return json({ user })
}

return json({ user, transaction })
}

export const action: ActionFunction = async ({ request }) => {
const user = await auth.isAuthenticated(request, {
failureRedirect: '/login',
})

const course = await getFirstCourse()

if (!course) {
return redirect('/dashboard')
}

const form = await request.formData()
const id = form.get('id')
const bankName = form.get('bankName')
const bankAccountNumber = form.get('bankAccountNumber')
const bankAccountName = form.get('bankAccountName')
const amount: string = form.get('amount') as string
const paymentTime: string = form.get('paymentTime') as string

const parsedAmount: number = parseInt(amount, 10) as number
const formattedPaymentTime: Date = new Date(paymentTime)

if (
typeof id !== 'string' ||
typeof bankName !== 'string' ||
typeof bankAccountNumber !== 'string' ||
typeof bankAccountName !== 'string' ||
typeof amount !== 'string' ||
typeof paymentTime !== 'string'
) {
return { formError: 'Form not submitted correctly.' }
}

const fieldErrors = {
bankName: validateRequired('Nomor WhatsApp', bankName),
bankAccountNumber: validateRequired('Nama Bank', bankAccountNumber),
bankAccountName: validateRequired('Nomor Rekening', bankAccountName),
amount: validateRequired('Nominal', amount),
paymentTime: validateRequired('Tanggal dan Waktu Pembayaran', paymentTime),
}

const fields: TransactionFields = {
userId: user.id,
courseId: course.id,
authorId: course.authorId,
bankName,
bankAccountName,
bankAccountNumber,
amount: parsedAmount,
datetime: formattedPaymentTime,
status: 'SUBMITTED',
}
if (Object.values(fieldErrors).some(Boolean)) {
return { fieldErrors, fields }
}

let transaction: Transaction | undefined

if (id) {
transaction = await db.transaction.update({
where: {
id,
},
data: fields,
})
} else {
transaction = await db.transaction.create({ data: fields })
}

if (!transaction) {
throw json({ fields }, { status: 500 })
}

return redirect(`/dashboard/purchase/verify/${transaction.id}`)
}

export default function PurchaseConfirm() {
const { user, transaction } = useLoaderData<{
user: User
transaction?: Transaction
}>()

// TODO: style form
return (
<>
<Form action="/dashboard/purchase/confirm" method="post">
<input type="hidden" name="id" value={transaction?.id} />
<div>
Nama Lengkap Anda: <span>{user.name}</span>
</div>
<div>
Nomor WhatsApp Anda: <span>{user.phoneNumber}</span>
</div>
<div>
Nama Lengkap atau Nomor WhatsApp Anda salah?{' '}
<Link to="/dashboard/profile/edit">Ubah di sini</Link>
</div>
<div>
<label>
Nama Bank:{' '}
<input
type="text"
name="bankName"
defaultValue={transaction?.bankName}
/>
</label>
</div>
<div>
<label>
Nomor Rekening:{' '}
<input
type="text"
name="bankAccountNumber"
defaultValue={transaction?.bankAccountNumber}
/>
</label>
</div>
<div>
<label>
Nama Pemilik Rekening:{' '}
<input
type="text"
name="bankAccountName"
defaultValue={transaction?.bankAccountName}
/>
</label>
</div>
<div>
<label>
Nominal:{' '}
<input
type="text"
name="amount"
defaultValue={transaction?.amount}
/>
</label>
</div>
<div>
<label>
Tanggal dan Waktu Pembayaran:
<input
type="datetime-local"
id="meeting-time"
name="paymentTime"
defaultValue={
transaction?.datetime
? formatDateTime(new Date(transaction.datetime))
: undefined
}
/>
</label>
</div>
<div>
<button
type="submit"
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Konfirmasi Pembayaran
</button>
</div>
</Form>
</>
)
}

export function CatchBoundary() {
const caught = useCatch<ThrownResponse>()

return (
<div className="border-4 border-dashed border-gray-200 rounded-lg h-96" />
<>
<div className="rounded-md bg-red-50 p-4 my-4">
<div className="flex">
<div className="shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">
{caught.statusText}
</h3>
</div>
</div>
</div>
<div className="py-16">
<div className="text-center">
<p className="text-sm font-semibold text-indigo-600 uppercase tracking-wide">
Error {caught.status}
</p>
<h1 className="mt-2 text-4xl font-extrabold text-gray-900 tracking-tight sm:text-5xl">
Terjadi kesalahan
</h1>
<div className="mt-6">
<Link
to="/dashboard/purchase/confirm"
className="text-base font-medium text-indigo-600 hover:text-indigo-500"
>
Silakan coba lagi<span aria-hidden="true"> &rarr;</span>
</Link>
</div>
</div>
</div>
</>
)
}
15 changes: 13 additions & 2 deletions app/routes/dashboard/purchase/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export default function Payment() {
import { Link } from 'remix'
import { STEPS } from '~/utils/constants'

export default function Purchase() {
return (
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
<div className="px-4 py-5 sm:px-6">
Expand Down Expand Up @@ -40,11 +43,19 @@ export default function Payment() {
<dd className="mt-1 text-sm text-gray-900">
Setelah melakukan transfer, simpan bukti transfer dalam bentuk
file gambar atau PDF, lalu klik tombol{' '}
<strong>Konfirmasi Pembayaran</strong> di bawah ini.
<strong>Saya sudah transfer</strong> di bawah ini.
</dd>
</div>
</dl>
</div>
<div className="px-4 py-3 bg-gray-50 text-right sm:px-6">
<Link
to={STEPS[1].pathname}
className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Saya sudah transfer
</Link>
</div>
</div>
)
}
5 changes: 0 additions & 5 deletions app/routes/dashboard/purchase/pending-verification.tsx

This file was deleted.

9 changes: 9 additions & 0 deletions app/routes/dashboard/purchase/verify.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Outlet } from 'react-router-dom'

export default function Verify() {
return (
<div className="border-4 border-dashed border-gray-200 rounded-lg h-96">
<Outlet />
</div>
)
}
Loading