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: cta to purchase the course #60

Merged
merged 9 commits into from
Dec 26, 2021
223 changes: 10 additions & 213 deletions app/routes/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,10 @@ import {
XIcon,
} from '@heroicons/react/outline'
import { SearchIcon } from '@heroicons/react/solid'

import type { User } from '@prisma/client'
import type { ActionFunction, LoaderFunction } from 'remix'
import {
redirect,
useActionData,
useTransition,
Form,
json,
useLoaderData,
} from 'remix'
import type { LoaderFunction } from 'remix'
import { Form, json, Outlet } from 'remix'
import { auth } from '~/services/auth.server'
import { LogoWithText } from '~/components/logo'
import { Button, Field } from '~/components/form-elements'
import { validatePhoneNumber, validateRequired } from '~/utils/validators'
import { db } from '~/utils/db.server'
import { getUser } from '~/models/user'
import { logout } from '~/services/session.server'

Expand All @@ -47,71 +35,18 @@ export const loader: LoaderFunction = async ({ request }) => {
return json({ user })
}

type ActionData = {
formError?: string
fieldErrors?: {
name: string | undefined
phoneNumber: string | undefined
instagram: string | undefined
telegram: string | undefined
}
fields?: {
name: string
phoneNumber: string
instagram: string
telegram: string
}
}

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

const form = await request.formData()
const name = form.get('name')
const phoneNumber = form.get('phoneNumber')
const instagram = form.get('instagram')
const telegram = form.get('telegram')
// TODO: Use `zod` instead
if (
typeof name !== 'string' ||
typeof phoneNumber !== 'string' ||
typeof instagram !== 'string' ||
typeof telegram !== 'string'
) {
return { formError: 'Form not submitted correctly.' }
}

const fieldErrors = {
name: validateRequired('Nama Lengkap', name),
phoneNumber: validatePhoneNumber('Nomor WhatsApp', phoneNumber),
}
const fields = { name, phoneNumber, instagram, telegram }
if (Object.values(fieldErrors).some(Boolean)) {
return { fieldErrors, fields }
}

await db.user.update({ where: { id: user.id }, data: fields })

return redirect('/dashboard')
}

const navigation = [
{ name: 'Home', href: '#', icon: HomeIcon, current: false },
{ name: 'Home', href: '/dashboard', icon: HomeIcon, current: true },
{ name: 'Jobs', href: '#', icon: BriefcaseIcon, current: false },
{ name: 'Applications', href: '#', icon: DocumentSearchIcon, current: false },
{ name: 'Messages', href: '#', icon: ChatIcon, current: false },
{ name: 'Team', href: '#', icon: UsersIcon, current: false },
{ name: 'Settings', href: '#', icon: CogIcon, current: true },
]
const tabs = [
{ name: 'General', href: '#', current: true },
{ name: 'Password', href: '#', current: false },
{ name: 'Notifications', href: '#', current: false },
{ name: 'Plan', href: '#', current: false },
{ name: 'Billing', href: '#', current: false },
{ name: 'Team Members', href: '#', current: false },
{
name: 'Settings',
href: '/dashboard/settings',
icon: CogIcon,
current: false,
},
]

function classNames(...classes: string[]) {
Expand All @@ -121,10 +56,6 @@ function classNames(...classes: string[]) {
export default function Dashboard() {
const [sidebarOpen, setSidebarOpen] = useState(false)

const { user } = useLoaderData<{ user: User }>()
const actionData = useActionData<ActionData>()
const { state } = useTransition()

return (
<>
<div>
Expand Down Expand Up @@ -338,141 +269,7 @@ export default function Dashboard() {
</div>

<main className="flex-1">
<div className="relative max-w-4xl mx-auto md:px-8 xl:px-0">
<div className="pt-10 pb-16">
<div className="px-4 sm:px-6 md:px-0">
<h1 className="text-3xl font-extrabold text-gray-900">
Settings
</h1>
</div>
<div className="px-4 sm:px-6 md:px-0">
<div className="py-6">
{/* Tabs */}
<div className="lg:hidden">
<label htmlFor="selected-tab" className="sr-only">
Select a tab
</label>
<select
id="selected-tab"
name="selected-tab"
className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm rounded-md"
defaultValue={tabs.find((tab) => tab.current)?.name}
>
{tabs.map((tab) => (
<option key={tab.name}>{tab.name}</option>
))}
</select>
</div>
<div className="hidden lg:block">
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.current
? 'border-purple-500 text-purple-600'
: 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700',
'whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm'
)}
>
{tab.name}
</a>
))}
</nav>
</div>
</div>
</div>

{/* Description list with inline editing */}
<div className="mt-10 sm:mt-0">
<div className="md:grid md:grid-cols-3 md:gap-6">
<div className="md:col-span-1">
<div className="px-4 sm:px-0">
<h3 className="text-lg font-medium leading-6 text-gray-900">
Data Diri
</h3>
<p className="mt-1 text-sm text-gray-600">
Untuk keperluan proses administrasi akun Anda.
</p>
</div>
</div>
<div className="mt-5 md:mt-0 md:col-span-2">
<Form action="/dashboard" method="post">
<div className="shadow overflow-hidden sm:rounded-md">
<div className="px-4 py-5 bg-white sm:p-6">
<div className="grid grid-cols-6 gap-6">
<Field
className="col-span-6"
name="name"
label="Nama Lengkap"
placeholder="Cantumkan nama lengkap Anda"
defaultValue={user.name ?? ''}
autoComplete="name"
autoCapitalize="words"
required
aria-invalid={user.name ? 'false' : 'true'}
/>
<Field
className="col-span-6 lg:col-span-3"
name="email"
label="Alamat Email"
placeholder="[email protected]"
defaultValue={user.email}
readOnly
autoComplete="tel"
/>
<Field
className="col-span-6 lg:col-span-3"
name="phoneNumber"
label="Nomor WhatsApp"
placeholder="+6281234567890"
defaultValue={
actionData?.fields?.phoneNumber ??
user.phoneNumber ??
''
}
error={actionData?.fieldErrors?.phoneNumber}
validator={validatePhoneNumber}
required
autoComplete="tel"
/>
<Field
className="col-span-6 lg:col-span-3"
name="telegram"
label="Username Telegram"
placeholder="@username"
defaultValue={user.telegram ?? ''}
autoComplete="nickname"
/>
<Field
className="col-span-6 lg:col-span-3"
name="instagram"
label="Username Instagram"
placeholder="@user.name"
defaultValue={user.instagram ?? ''}
autoComplete="nickname"
/>
</div>
</div>
<div className="px-4 py-3 bg-gray-50 text-right sm:px-6">
<Button
type="submit"
disabled={state === 'submitting'}
className="inline-flex"
>
Simpan
</Button>
</div>
</div>
</Form>
</div>
</div>
</div>
</div>
</div>
</div>
<Outlet />
</main>
</div>
</div>
Expand Down
105 changes: 105 additions & 0 deletions app/routes/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { CheckCircleIcon } from '@heroicons/react/solid'
import { Link } from 'remix'

const includedFeatures = [
'Handout berupa catatan bergambar',
'Akses kelas online melalui Zoom',
'Printable planner',
'Video rekaman kelas',
]

export default function Dashboard() {
return (
<div className="bg-gray-100">
<div className="pt-12 sm:pt-16 lg:pt-20">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center">
<h2 className="text-3xl font-extrabold text-gray-900 sm:text-4xl lg:text-5xl">
Biaya kelas
</h2>
<p className="mt-4 text-xl text-gray-600">
Biaya baru dibayarkan setelah Anda terkonfirmasi sebagai peserta
kelas
</p>
</div>
</div>
</div>
<div className="mt-8 bg-white pb-16 sm:mt-12 sm:pb-20 lg:pb-28">
<div className="relative">
<div className="absolute inset-0 h-1/2 bg-gray-100" />
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="max-w-lg mx-auto rounded-lg shadow-lg overflow-hidden lg:max-w-none lg:flex">
<div className="flex-1 bg-white px-6 py-8 lg:p-12">
<h3 className="text-2xl font-extrabold text-gray-900 sm:text-3xl">
Terbatas untuk 30 orang peserta
</h3>
<p className="mt-6 text-base text-gray-500">
Apabila Anda berubah pikiran, kabari kami setidaknya tiga hari
sebelum kelas dimulai supaya kami dapat mengembalikan dana
Anda sekaligus membuka slot untuk calon peserta kelas lainnya.
</p>
<div className="mt-8">
<div className="flex items-center">
<h4 className="flex-shrink-0 pr-4 bg-white text-sm tracking-wider font-semibold uppercase text-indigo-600">
Biaya termasuk
</h4>
<div className="flex-1 border-t-2 border-gray-200" />
</div>
<ul className="mt-8 space-y-5 lg:space-y-0 lg:grid lg:grid-cols-2 lg:gap-x-8 lg:gap-y-5">
{includedFeatures.map((feature) => (
<li
key={feature}
className="flex items-start lg:col-span-1"
>
<div className="flex-shrink-0">
<CheckCircleIcon
className="h-5 w-5 text-green-400"
aria-hidden="true"
/>
</div>
<p className="ml-3 text-sm text-gray-700">{feature}</p>
</li>
))}
</ul>
</div>
</div>
<div className="py-8 px-6 text-center bg-gray-50 lg:flex-shrink-0 lg:flex lg:flex-col lg:justify-center lg:p-12">
<p className="text-lg leading-6 font-medium text-gray-900">
Sekali bayar
</p>
<div className="mt-4 flex items-center justify-center text-5xl font-extrabold text-gray-900">
<span className="ml-3 text-xl font-medium text-gray-500">
Rp
</span>
<span>XX0.000</span>
<span className="ml-3 text-xl font-medium text-gray-500">
,-
</span>
</div>
<p className="mt-4 text-sm">
<Link
to="/#privacy-policy"
className="font-medium text-gray-500 underline"
target="_blank"
>
Kebijakan privasi
</Link>
</p>
<div className="mt-6">
<div className="rounded-md shadow">
<Link
to="/dashboard/purchase"
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>
</div>
</div>
</div>
</div>
</div>
)
}
3 changes: 3 additions & 0 deletions app/routes/dashboard/purchase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Purchase() {
return <h1>Purchase page</h1>
}
Loading