Skip to content

Commit

Permalink
feat: Whatsapp Link (#83)
Browse files Browse the repository at this point in the history
feat: Whatsapp Link
  • Loading branch information
kodiakhq[bot] authored Feb 26, 2022
2 parents a144f51 + c9941ef commit 3853d54
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 13 deletions.
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

0 comments on commit 3853d54

Please sign in to comment.