zainfathoni / kelas.rumahberbagi.com Goto Github PK
View Code? Open in Web Editor NEWKelas Rumah Berbagi
Home Page: https://kelas.rumahberbagi.com
License: MIT License
Kelas Rumah Berbagi
Home Page: https://kelas.rumahberbagi.com
License: MIT License
Rename the /dashboard/settings
route into /dashboard/profile/edit
route by renaming /app/route/dashboard/settings.tsx
file into /app/route/dashboard/profile/edit.tsx
and update the navigation items accordingly.
This issue is a part of both the #35 & #71 issues. It should be done under #35.
Deploy the app to production and staging environments.
I realized that it's much simpler to remove the indirect relationships between the Transaction table and the Course entity. Therefore, in PR #91, I updated the Transaction entity not related to the Subscription entity.
Please update the database schema to reflect the changes.
schema.prisma
file according to the latest Entity Relationship Diagramnpx prisma migrate dev --name update_transaction_relationship
, as documented hereUpdate the database schema to support the application flow. Please refer to the Entity Relationship Diagram.
schema.prisma
file accordinglyseed.ts
file to provide sufficient data for end-to-end testingApplication Shell for authenticated pages
/dashboard
route,Dashboard page should render this Light sidebar with light background layout while keeping the Logout
button intact in the sidebar.
Replace the Avatar
component with Circular avatars with placeholder initials
/dashboard
route,/* This example requires Tailwind CSS v2.0+ */
import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import {
CalendarIcon,
ChartBarIcon,
FolderIcon,
HomeIcon,
InboxIcon,
MenuIcon,
UsersIcon,
XIcon,
} from '@heroicons/react/outline'
const navigation = [
{ name: 'Dashboard', href: '#', icon: HomeIcon, current: true },
{ name: 'Team', href: '#', icon: UsersIcon, current: false },
{ name: 'Projects', href: '#', icon: FolderIcon, current: false },
{ name: 'Calendar', href: '#', icon: CalendarIcon, current: false },
{ name: 'Documents', href: '#', icon: InboxIcon, current: false },
{ name: 'Reports', href: '#', icon: ChartBarIcon, current: false },
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Example() {
const [sidebarOpen, setSidebarOpen] = useState(false)
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full">
<body class="h-full">
```
*/}
<div>
<Transition.Root show={sidebarOpen} as={Fragment}>
<Dialog as="div" className="fixed inset-0 flex z-40 md:hidden" onClose={setSidebarOpen}>
<Transition.Child
as={Fragment}
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-600 bg-opacity-75" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition ease-in-out duration-300 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>
<div className="relative flex-1 flex flex-col max-w-xs w-full bg-white">
<Transition.Child
as={Fragment}
enter="ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute top-0 right-0 -mr-12 pt-2">
<button
type="button"
className="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
onClick={() => setSidebarOpen(false)}
>
<span className="sr-only">Close sidebar</span>
<XIcon className="h-6 w-6 text-white" aria-hidden="true" />
</button>
</div>
</Transition.Child>
<div className="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
<div className="flex-shrink-0 flex items-center px-4">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-indigo-600-mark-gray-800-text.svg"
alt="Workflow"
/>
</div>
<nav className="mt-5 px-2 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-100 text-gray-900'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900',
'group flex items-center px-2 py-2 text-base font-medium rounded-md'
)}
>
<item.icon
className={classNames(
item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500',
'mr-4 flex-shrink-0 h-6 w-6'
)}
aria-hidden="true"
/>
{item.name}
</a>
))}
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 group block">
<div className="flex items-center">
<div>
<img
className="inline-block h-10 w-10 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div className="ml-3">
<p className="text-base font-medium text-gray-700 group-hover:text-gray-900">Tom Cook</p>
<p className="text-sm font-medium text-gray-500 group-hover:text-gray-700">View profile</p>
</div>
</div>
</a>
</div>
</div>
</Transition.Child>
<div className="flex-shrink-0 w-14">{/* Force sidebar to shrink to fit close icon */}</div>
</Dialog>
</Transition.Root>
{/* Static sidebar for desktop */}
<div className="hidden md:flex md:w-64 md:flex-col md:fixed md:inset-y-0">
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex-1 flex flex-col min-h-0 border-r border-gray-200 bg-white">
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-indigo-600-mark-gray-800-text.svg"
alt="Workflow"
/>
</div>
<nav className="mt-5 flex-1 px-2 bg-white space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900',
'group flex items-center px-2 py-2 text-sm font-medium rounded-md'
)}
>
<item.icon
className={classNames(
item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500',
'mr-3 flex-shrink-0 h-6 w-6'
)}
aria-hidden="true"
/>
{item.name}
</a>
))}
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 w-full group block">
<div className="flex items-center">
<div>
<img
className="inline-block h-9 w-9 rounded-full"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
alt=""
/>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-700 group-hover:text-gray-900">Tom Cook</p>
<p className="text-xs font-medium text-gray-500 group-hover:text-gray-700">View profile</p>
</div>
</div>
</a>
</div>
</div>
</div>
<div className="md:pl-64 flex flex-col flex-1">
<div className="sticky top-0 z-10 md:hidden pl-1 pt-1 sm:pl-3 sm:pt-3 bg-white">
<button
type="button"
className="-ml-0.5 -mt-0.5 h-12 w-12 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<MenuIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<main className="flex-1">
<div className="py-6">
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
<h1 className="text-2xl font-semibold text-gray-900">Dashboard</h1>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6 md:px-8">
{/* Replace with your content */}
<div className="py-4">
<div className="border-4 border-dashed border-gray-200 rounded-lg h-96" />
</div>
{/* /End replace */}
</div>
</div>
</main>
</div>
</div>
</>
)
}
Outlet
in dashboard. tsx file by referencing the code snippet above to match the expected outcome in the screenshot.Profile
item should be active when the current route is /dashboard/profile
.Avatar
component with Circular avatars with placeholder initialsSettings
item functionality with the View profile
button in the sidebar.Logout
item is rendered right on top of the View profile
item in the sidebar.kelas.rumahberbagi.com/e2e/magic.spec.ts
Line 13 in f7c97d7
Implement the WhatsApp interaction parts of the Transaction Flow. It requires #75 as a prerequisite.
The /dashboard/purchase/confirm
route should store the transaction values in the database and compose a prefilled WhatsApp link for the Member to send to the Author.
Step no. 6 (WhatsApp Link) in the Transaction Flow diagram.
Silakan klik tombol di bawah ini untuk mengirimkan pesan WhatsApp kepada kami. Setelah itu, tolong lampirkan bukti transfer berupa foto atau file PDF ke nomor WhatsApp tersebut. Terima kasih.
[ Kirim Pesan WhatsApp ]
`https://api.whatsapp.com/send?phone=${authorPhoneNumber}&text=%5BKelas%20Tahun%20Prasekolahku%5D%0A%0AKlik%20di%20sini%20untuk%20verifikasi%20pembayaran%0Ahttps%3A%2F%2Frbagi.id%2Fverify%2F${transactionId}%0A%0ABerikut%20terlampir%20foto%2Ffile%20bukti%20pembayaran%20saya%3A`
P.S. I populated that link above through https://create.wa.link, you can try experimenting with it to provide a better WhatsApp message
/dashboard/purchase/confirm
route correctly,/* This example requires Tailwind CSS v2.0+ */
import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CheckIcon } from '@heroicons/react/outline'
export default function Example() {
const [open, setOpen] = useState(true)
return (
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="fixed z-10 inset-0 overflow-y-auto" onClose={setOpen}>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
​
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6">
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
<CheckIcon className="h-6 w-6 text-green-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:mt-5">
<Dialog.Title as="h3" className="text-lg leading-6 font-medium text-gray-900">
Payment successful
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur amet labore.
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-6">
<button
type="button"
className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:text-sm"
onClick={() => setOpen(false)}
>
Go back to dashboard
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
)
}
/dashboard/purchase/verify/$transactionId
route upon a successful response/dashboard/purchase/verify/$transactionId.tsx
file/dashboard/purchase/verify.tsx
file to render the modal aboveImplement the View Transaction Details parts of the Transaction Flow. It can only be closed after we close #79.
The /dashboard/transactions/$transactionId
route should load the user and the transaction information from the database and show it to the Author. Please compose the UI based on this Stacked card layout screen.
Step no. 10-15 (View Transaction Details) in the Transaction Flow diagram.
`https://api.whatsapp.com/send?phone=${memberPhoneNumber}&text=Maaf%2C%20kami%20belum%20menerima%20pembayaran%20Anda%20untuk%20transaksi%20ini%3A%0Ahttps%3A%2F%2Frbagi.id%2Fpending%2F${$transactionId}`
PS I populated that link above through https://create.wa.link, you can try experimenting with it to provide a better WhatsApp message
/dashboard/transactions/$transactionId
route,$transactionId
,import { Fragment } from 'react'
import { Menu, Popover, Transition } from '@headlessui/react'
import {
ArrowNarrowLeftIcon,
CheckIcon,
HomeIcon,
PaperClipIcon,
QuestionMarkCircleIcon,
SearchIcon,
ThumbUpIcon,
UserIcon,
} from '@heroicons/react/solid'
import { BellIcon, MenuIcon, XIcon } from '@heroicons/react/outline'
const user = {
name: 'Whitney Francis',
email: '[email protected]',
imageUrl:
'https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=256&h=256&q=80',
}
const navigation = [
{ name: 'Dashboard', href: '#' },
{ name: 'Jobs', href: '#' },
{ name: 'Applicants', href: '#' },
{ name: 'Company', href: '#' },
]
const breadcrumbs = [
{ name: 'Jobs', href: '#', current: false },
{ name: 'Front End Developer', href: '#', current: false },
{ name: 'Applicants', href: '#', current: true },
]
const userNavigation = [
{ name: 'Your Profile', href: '#' },
{ name: 'Settings', href: '#' },
{ name: 'Sign out', href: '#' },
]
const attachments = [
{ name: 'resume_front_end_developer.pdf', href: '#' },
{ name: 'coverletter_front_end_developer.pdf', href: '#' },
]
const eventTypes = {
applied: { icon: UserIcon, bgColorClass: 'bg-gray-400' },
advanced: { icon: ThumbUpIcon, bgColorClass: 'bg-blue-500' },
completed: { icon: CheckIcon, bgColorClass: 'bg-green-500' },
}
const timeline = [
{
id: 1,
type: eventTypes.applied,
content: 'Applied to',
target: 'Front End Developer',
date: 'Sep 20',
datetime: '2020-09-20',
},
{
id: 2,
type: eventTypes.advanced,
content: 'Advanced to phone screening by',
target: 'Bethany Blake',
date: 'Sep 22',
datetime: '2020-09-22',
},
{
id: 3,
type: eventTypes.completed,
content: 'Completed phone screening with',
target: 'Martha Gardner',
date: 'Sep 28',
datetime: '2020-09-28',
},
{
id: 4,
type: eventTypes.advanced,
content: 'Advanced to interview by',
target: 'Bethany Blake',
date: 'Sep 30',
datetime: '2020-09-30',
},
{
id: 5,
type: eventTypes.completed,
content: 'Completed interview with',
target: 'Katherine Snyder',
date: 'Oct 4',
datetime: '2020-10-04',
},
]
const comments = [
{
id: 1,
name: 'Leslie Alexander',
date: '4d ago',
imageId: '1494790108377-be9c29b29330',
body:
'Ducimus quas delectus ad maxime totam doloribus reiciendis ex. Tempore dolorem maiores. Similique voluptatibus tempore non ut.',
},
{
id: 2,
name: 'Michael Foster',
date: '4d ago',
imageId: '1519244703995-f4e0f30006d5',
body:
'Et ut autem. Voluptatem eum dolores sint necessitatibus quos. Quis eum qui dolorem accusantium voluptas voluptatem ipsum. Quo facere iusto quia accusamus veniam id explicabo et aut.',
},
{
id: 3,
name: 'Dries Vincent',
date: '4d ago',
imageId: '1506794778202-cad84cf45f1d',
body:
'Expedita consequatur sit ea voluptas quo ipsam recusandae. Ab sint et voluptatem repudiandae voluptatem et eveniet. Nihil quas consequatur autem. Perferendis rerum et.',
},
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Example() {
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full bg-gray-100">
<body class="h-full">
```
*/}
<div className="min-h-full">
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto px-2 sm:px-4 lg:px-8">
<Popover className="flex justify-between h-16">
<div className="flex px-2 lg:px-0">
<div className="flex-shrink-0 flex items-center">
<a href="#">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-mark-blue-600.svg"
alt="Workflow"
/>
</a>
</div>
<nav aria-label="Global" className="hidden lg:ml-6 lg:flex lg:items-center lg:space-x-4">
{navigation.map((item) => (
<a key={item.name} href={item.href} className="px-3 py-2 text-gray-900 text-sm font-medium">
{item.name}
</a>
))}
</nav>
</div>
<div className="flex-1 flex items-center justify-center px-2 lg:ml-6 lg:justify-end">
<div className="max-w-lg w-full lg:max-w-xs">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
<input
id="search"
name="search"
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white shadow-sm placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-600 focus:border-blue-600 sm:text-sm"
placeholder="Search"
type="search"
/>
</div>
</div>
</div>
<div className="flex items-center lg:hidden">
{/* Mobile menu button */}
<Popover.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500">
<span className="sr-only">Open main menu</span>
<MenuIcon className="block h-6 w-6" aria-hidden="true" />
</Popover.Button>
</div>
<Transition.Root as={Fragment}>
<div className="lg:hidden">
<Transition.Child
as={Fragment}
enter="duration-150 ease-out"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="duration-150 ease-in"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Popover.Overlay className="z-20 fixed inset-0 bg-black bg-opacity-25" aria-hidden="true" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="duration-150 ease-out"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="duration-150 ease-in"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Popover.Panel
focus
className="z-30 absolute top-0 right-0 max-w-none w-full p-2 transition transform origin-top"
>
<div className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 bg-white divide-y divide-gray-200">
<div className="pt-3 pb-2">
<div className="flex items-center justify-between px-4">
<div>
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-mark-blue-600.svg"
alt="Workflow"
/>
</div>
<div className="-mr-2">
<Popover.Button className="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500">
<span className="sr-only">Close menu</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
</Popover.Button>
</div>
</div>
<div className="mt-3 px-2 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className="block rounded-md px-3 py-2 text-base text-gray-900 font-medium hover:bg-gray-100 hover:text-gray-800"
>
{item.name}
</a>
))}
</div>
</div>
<div className="pt-4 pb-2">
<div className="flex items-center px-5">
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src={user.imageUrl} alt="" />
</div>
<div className="ml-3">
<div className="text-base font-medium text-gray-800">{user.name}</div>
<div className="text-sm font-medium text-gray-500">{user.email}</div>
</div>
<button
type="button"
className="ml-auto flex-shrink-0 bg-white p-1 text-gray-400 rounded-full hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<span className="sr-only">View notifications</span>
<BellIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="mt-3 px-2 space-y-1">
{userNavigation.map((item) => (
<a
key={item.name}
href={item.href}
className="block rounded-md px-3 py-2 text-base text-gray-900 font-medium hover:bg-gray-100 hover:text-gray-800"
>
{item.name}
</a>
))}
</div>
</div>
</div>
</Popover.Panel>
</Transition.Child>
</div>
</Transition.Root>
<div className="hidden lg:ml-4 lg:flex lg:items-center">
<button
type="button"
className="flex-shrink-0 bg-white p-1 text-gray-400 rounded-full hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<span className="sr-only">View notifications</span>
<BellIcon className="h-6 w-6" aria-hidden="true" />
</button>
{/* Profile dropdown */}
<Menu as="div" className="ml-4 relative flex-shrink-0">
<div>
<Menu.Button className="bg-white rounded-full flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<span className="sr-only">Open user menu</span>
<img className="h-8 w-8 rounded-full" src={user.imageUrl} alt="" />
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
{userNavigation.map((item) => (
<Menu.Item key={item.name}>
{({ active }) => (
<a
href={item.href}
className={classNames(
active ? 'bg-gray-100' : '',
'block px-4 py-2 text-sm text-gray-700'
)}
>
{item.name}
</a>
)}
</Menu.Item>
))}
</Menu.Items>
</Transition>
</Menu>
</div>
</Popover>
</div>
<div className="max-w-7xl mx-auto px-4 sm:px-6">
<div className="border-t border-gray-200 py-3">
<nav className="flex" aria-label="Breadcrumb">
<div className="flex sm:hidden">
<a
href="#"
className="group inline-flex space-x-3 text-sm font-medium text-gray-500 hover:text-gray-700"
>
<ArrowNarrowLeftIcon
className="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-600"
aria-hidden="true"
/>
<span>Back to Applicants</span>
</a>
</div>
<div className="hidden sm:block">
<ol role="list" className="flex items-center space-x-4">
<li>
<div>
<a href="#" className="text-gray-400 hover:text-gray-500">
<HomeIcon className="flex-shrink-0 h-5 w-5" aria-hidden="true" />
<span className="sr-only">Home</span>
</a>
</div>
</li>
{breadcrumbs.map((item) => (
<li key={item.name}>
<div className="flex items-center">
<svg
className="flex-shrink-0 h-5 w-5 text-gray-300"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 20 20"
aria-hidden="true"
>
<path d="M5.555 17.776l8-16 .894.448-8 16-.894-.448z" />
</svg>
<a
href={item.href}
className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
</div>
</li>
))}
</ol>
</div>
</nav>
</div>
</div>
</header>
<main className="py-10">
{/* Page header */}
<div className="max-w-3xl mx-auto px-4 sm:px-6 md:flex md:items-center md:justify-between md:space-x-5 lg:max-w-7xl lg:px-8">
<div className="flex items-center space-x-5">
<div className="flex-shrink-0">
<div className="relative">
<img
className="h-16 w-16 rounded-full"
src="https://images.unsplash.com/photo-1463453091185-61582044d556?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=1024&h=1024&q=80"
alt=""
/>
<span className="absolute inset-0 shadow-inner rounded-full" aria-hidden="true" />
</div>
</div>
<div>
<h1 className="text-2xl font-bold text-gray-900">Ricardo Cooper</h1>
<p className="text-sm font-medium text-gray-500">
Applied for{' '}
<a href="#" className="text-gray-900">
Front End Developer
</a>{' '}
on <time dateTime="2020-08-25">August 25, 2020</time>
</p>
</div>
</div>
<div className="mt-6 flex flex-col-reverse justify-stretch space-y-4 space-y-reverse sm:flex-row-reverse sm:justify-end sm:space-x-reverse sm:space-y-0 sm:space-x-3 md:mt-0 md:flex-row md:space-x-3">
<button
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 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"
>
Disqualify
</button>
<button
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-blue-500"
>
Advance to offer
</button>
</div>
</div>
<div className="mt-8 max-w-3xl mx-auto grid grid-cols-1 gap-6 sm:px-6 lg:max-w-7xl lg:grid-flow-col-dense lg:grid-cols-3">
<div className="space-y-6 lg:col-start-1 lg:col-span-2">
{/* Description list*/}
<section aria-labelledby="applicant-information-title">
<div className="bg-white shadow sm:rounded-lg">
<div className="px-4 py-5 sm:px-6">
<h2 id="applicant-information-title" className="text-lg leading-6 font-medium text-gray-900">
Applicant Information
</h2>
<p className="mt-1 max-w-2xl text-sm text-gray-500">Personal details and application.</p>
</div>
<div className="border-t border-gray-200 px-4 py-5 sm:px-6">
<dl className="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">Application for</dt>
<dd className="mt-1 text-sm text-gray-900">Backend Developer</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">Email address</dt>
<dd className="mt-1 text-sm text-gray-900">[email protected]</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">Salary expectation</dt>
<dd className="mt-1 text-sm text-gray-900">$120,000</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">Phone</dt>
<dd className="mt-1 text-sm text-gray-900">+1 555-555-5555</dd>
</div>
<div className="sm:col-span-2">
<dt className="text-sm font-medium text-gray-500">About</dt>
<dd className="mt-1 text-sm text-gray-900">
Fugiat ipsum ipsum deserunt culpa aute sint do nostrud anim incididunt cillum culpa consequat.
Excepteur qui ipsum aliquip consequat sint. Sit id mollit nulla mollit nostrud in ea officia
proident. Irure nostrud pariatur mollit ad adipisicing reprehenderit deserunt qui eu.
</dd>
</div>
<div className="sm:col-span-2">
<dt className="text-sm font-medium text-gray-500">Attachments</dt>
<dd className="mt-1 text-sm text-gray-900">
<ul role="list" className="border border-gray-200 rounded-md divide-y divide-gray-200">
{attachments.map((attachment) => (
<li
key={attachment.name}
className="pl-3 pr-4 py-3 flex items-center justify-between text-sm"
>
<div className="w-0 flex-1 flex items-center">
<PaperClipIcon className="flex-shrink-0 h-5 w-5 text-gray-400" aria-hidden="true" />
<span className="ml-2 flex-1 w-0 truncate">{attachment.name}</span>
</div>
<div className="ml-4 flex-shrink-0">
<a href={attachment.href} className="font-medium text-blue-600 hover:text-blue-500">
Download
</a>
</div>
</li>
))}
</ul>
</dd>
</div>
</dl>
</div>
<div>
<a
href="#"
className="block bg-gray-50 text-sm font-medium text-gray-500 text-center px-4 py-4 hover:text-gray-700 sm:rounded-b-lg"
>
Read full application
</a>
</div>
</div>
</section>
{/* Comments*/}
<section aria-labelledby="notes-title">
<div className="bg-white shadow sm:rounded-lg sm:overflow-hidden">
<div className="divide-y divide-gray-200">
<div className="px-4 py-5 sm:px-6">
<h2 id="notes-title" className="text-lg font-medium text-gray-900">
Notes
</h2>
</div>
<div className="px-4 py-6 sm:px-6">
<ul role="list" className="space-y-8">
{comments.map((comment) => (
<li key={comment.id}>
<div className="flex space-x-3">
<div className="flex-shrink-0">
<img
className="h-10 w-10 rounded-full"
src={`https://images.unsplash.com/photo-${comment.imageId}?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80`}
alt=""
/>
</div>
<div>
<div className="text-sm">
<a href="#" className="font-medium text-gray-900">
{comment.name}
</a>
</div>
<div className="mt-1 text-sm text-gray-700">
<p>{comment.body}</p>
</div>
<div className="mt-2 text-sm space-x-2">
<span className="text-gray-500 font-medium">{comment.date}</span>{' '}
<span className="text-gray-500 font-medium">·</span>{' '}
<button type="button" className="text-gray-900 font-medium">
Reply
</button>
</div>
</div>
</div>
</li>
))}
</ul>
</div>
</div>
<div className="bg-gray-50 px-4 py-6 sm:px-6">
<div className="flex space-x-3">
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src={user.imageUrl} alt="" />
</div>
<div className="min-w-0 flex-1">
<form action="#">
<div>
<label htmlFor="comment" className="sr-only">
About
</label>
<textarea
id="comment"
name="comment"
rows={3}
className="shadow-sm block w-full focus:ring-blue-500 focus:border-blue-500 sm:text-sm border border-gray-300 rounded-md"
placeholder="Add a note"
defaultValue={''}
/>
</div>
<div className="mt-3 flex items-center justify-between">
<a
href="#"
className="group inline-flex items-start text-sm space-x-2 text-gray-500 hover:text-gray-900"
>
<QuestionMarkCircleIcon
className="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
<span>Some HTML is okay.</span>
</a>
<button
type="submit"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Comment
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
</div>
<section aria-labelledby="timeline-title" className="lg:col-start-3 lg:col-span-1">
<div className="bg-white px-4 py-5 shadow sm:rounded-lg sm:px-6">
<h2 id="timeline-title" className="text-lg font-medium text-gray-900">
Timeline
</h2>
{/* Activity Feed */}
<div className="mt-6 flow-root">
<ul role="list" className="-mb-8">
{timeline.map((item, itemIdx) => (
<li key={item.id}>
<div className="relative pb-8">
{itemIdx !== timeline.length - 1 ? (
<span
className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200"
aria-hidden="true"
/>
) : null}
<div className="relative flex space-x-3">
<div>
<span
className={classNames(
item.type.bgColorClass,
'h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white'
)}
>
<item.type.icon className="w-5 h-5 text-white" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
<div>
<p className="text-sm text-gray-500">
{item.content}{' '}
<a href="#" className="font-medium text-gray-900">
{item.target}
</a>
</p>
</div>
<div className="text-right text-sm whitespace-nowrap text-gray-500">
<time dateTime={item.datetime}>{item.date}</time>
</div>
</div>
</div>
</div>
</li>
))}
</ul>
</div>
<div className="mt-6 flex flex-col justify-stretch">
<button
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Advance to offer
</button>
</div>
</div>
</section>
</div>
</main>
</div>
</>
)
}
app/route/dashboard/transactions.tsx
file as a route shell rendering nothing other than Remix's Outlet componentapp/route/dashboard/transactions/index.tsx
rendering #92.app/route/dashboard/transactions/$transactionId.tsx
rendering the Stacked card layout screen containing a dummy user and the transaction detailsapp/route/dashboard/transactions/$transactionId.tsx
file to load the user and the transaction data then show the data into the screenDuplicate app.rumahberbagi.com
Please only update from the hero section downwards. Don't touch the Header
component because it's a part of the login flow.
Unfortunately, toBeVisible()
must be used with the Locator object because I'm getting the error Error: toBeVisible can be only used with Locator object
when using the getByRole
query.
const heading = await getByRole('heading', {
name: /biaya kelas/i,
})
await expect(heading).toBeVisible()
Originally posted by @retry19 in #60 (comment)
Prepare visual design mockups (without application logic) using Tailwind UI. Please refer to the Transaction Flow diagram and Routes Table.
Implement the payment confirmation parts of the Transaction Flow.
The purchase route should render this Two-column in card by default.
Step no. 2 (Instructions) in the Transaction Flow diagram.
Instruksi Pembayaran
Transfer uang dengan nominal dan nomor rekening berikut ini
Nominal
Rp XX0.000,-
Nama Bank
Jago (Bank Artos)
Kode Bank
542
Nomor Rekening
5041 7623 2889
Nama Penerima
Zain Fathoni
Instruksi Konfirmasi
Setelah melakukan transfer, simpan bukti transfer dalam bentuk file gambar atau PDF, lalu klik tombol Konfirmasi Pembayaran di bawah ini.
/dashboard/purchase
route,/dashboard/purchase/confirm
route.Step no. 5 (Confirm Payment) in the Transaction Flow diagram.
/dashboard/purchase/confirm
route,/dashboard/purchase/pending-verification
route.export default function Example() {
return (
<>
<button
type="button"
className="inline-flex items-center px-2.5 py-1.5 border border-transparent text-xs font-medium rounded shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Button text
</button>
<button
type="button"
className="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Button text
</button>
<button
type="button"
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Button text
</button>
<button
type="button"
className="inline-flex items-center px-4 py-2 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Button text
</button>
<button
type="button"
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Button text
</button>
</>
)
}
/* This example requires Tailwind CSS v2.0+ */
import { PaperClipIcon } from '@heroicons/react/solid'
export default function Example() {
return (
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
<div className="px-4 py-5 sm:px-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">Applicant Information</h3>
<p className="mt-1 max-w-2xl text-sm text-gray-500">Personal details and application.</p>
</div>
<div className="border-t border-gray-200 px-4 py-5 sm:px-6">
<dl className="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">Full name</dt>
<dd className="mt-1 text-sm text-gray-900">Margot Foster</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">Application for</dt>
<dd className="mt-1 text-sm text-gray-900">Backend Developer</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">Email address</dt>
<dd className="mt-1 text-sm text-gray-900">[email protected]</dd>
</div>
<div className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">Salary expectation</dt>
<dd className="mt-1 text-sm text-gray-900">$120,000</dd>
</div>
<div className="sm:col-span-2">
<dt className="text-sm font-medium text-gray-500">About</dt>
<dd className="mt-1 text-sm text-gray-900">
Fugiat ipsum ipsum deserunt culpa aute sint do nostrud anim incididunt cillum culpa consequat. Excepteur
qui ipsum aliquip consequat sint. Sit id mollit nulla mollit nostrud in ea officia proident. Irure nostrud
pariatur mollit ad adipisicing reprehenderit deserunt qui eu.
</dd>
</div>
<div className="sm:col-span-2">
<dt className="text-sm font-medium text-gray-500">Attachments</dt>
<dd className="mt-1 text-sm text-gray-900">
<ul role="list" className="border border-gray-200 rounded-md divide-y divide-gray-200">
<li className="pl-3 pr-4 py-3 flex items-center justify-between text-sm">
<div className="w-0 flex-1 flex items-center">
<PaperClipIcon className="flex-shrink-0 h-5 w-5 text-gray-400" aria-hidden="true" />
<span className="ml-2 flex-1 w-0 truncate">resume_back_end_developer.pdf</span>
</div>
<div className="ml-4 flex-shrink-0">
<a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">
Download
</a>
</div>
</li>
<li className="pl-3 pr-4 py-3 flex items-center justify-between text-sm">
<div className="w-0 flex-1 flex items-center">
<PaperClipIcon className="flex-shrink-0 h-5 w-5 text-gray-400" aria-hidden="true" />
<span className="ml-2 flex-1 w-0 truncate">coverletter_back_end_developer.pdf</span>
</div>
<div className="ml-4 flex-shrink-0">
<a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">
Download
</a>
</div>
</li>
</ul>
</dd>
</div>
</dl>
</div>
</div>
)
}
dashboard/purchase/confirm.tsx
file/dashboard/purchase/pending-verification.tsx
fileWhen running npm run test:e2e:run
command in local machines, sometimes the developers experience intermittent issues in these tests:
login.spec.ts
Thanks to our fellow contributors, we managed to work for multiple streams in parallel.
Unfortunately, our end-to-end test cannot test the upstream works without the downstream elements implemented, e.g. it couldn't test the database schema and operations without the form implementation.
Therefore, It's time to introduce a proper unit and integration tests infrastructure.
You can refer to this PR as an example: kawalcovid19/wargabantuwarga.com#358.
It requires #33 as a prerequisite.
test:coverage
script in CI, call the check Unit and integration testsImplement a comprehensive and valid application logic with all its edge cases.
Prepare a production-ready database.
Integrate Docker to make the database portable across machines.
Use PlanetScale for hosting the database.
Consider using Cloudflare Workers and its Relational Database Connectors
Implement the User Details page to show particular user details. We can do it after #94.
The /dashboard/users/$userId
route should load the user information from the database and show it to the Author. Please compose the UI based on this Multi-column directory screen.
/dashboard/users/$userId
route,$userId
,UI-wise, you can reuse the components developed at #71 with some minor tweaks if necessary.
import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import {
CalendarIcon,
CogIcon,
HomeIcon,
MapIcon,
MenuIcon,
SearchCircleIcon,
SpeakerphoneIcon,
UserGroupIcon,
ViewGridAddIcon,
XIcon,
} from '@heroicons/react/outline'
import { ChevronLeftIcon, FilterIcon, MailIcon, PhoneIcon, SearchIcon } from '@heroicons/react/solid'
const user = {
name: 'Tom Cook',
imageUrl:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
}
const navigation = [
{ name: 'Dashboard', href: '#', icon: HomeIcon, current: false },
{ name: 'Calendar', href: '#', icon: CalendarIcon, current: false },
{ name: 'Teams', href: '#', icon: UserGroupIcon, current: false },
{ name: 'Directory', href: '#', icon: SearchCircleIcon, current: true },
{ name: 'Announcements', href: '#', icon: SpeakerphoneIcon, current: false },
{ name: 'Office Map', href: '#', icon: MapIcon, current: false },
]
const secondaryNavigation = [
{ name: 'Apps', href: '#', icon: ViewGridAddIcon },
{ name: 'Settings', href: '#', icon: CogIcon },
]
const tabs = [
{ name: 'Profile', href: '#', current: true },
{ name: 'Calendar', href: '#', current: false },
{ name: 'Recognition', href: '#', current: false },
]
const profile = {
name: 'Ricardo Cooper',
imageUrl:
'https://images.unsplash.com/photo-1463453091185-61582044d556?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=1024&h=1024&q=80',
coverImageUrl:
'https://images.unsplash.com/photo-1444628838545-ac4016a5418a?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80',
about: `
<p>Tincidunt quam neque in cursus viverra orci, dapibus nec tristique. Nullam ut sit dolor consectetur urna, dui cras nec sed. Cursus risus congue arcu aenean posuere aliquam.</p>
<p>Et vivamus lorem pulvinar nascetur non. Pulvinar a sed platea rhoncus ac mauris amet. Urna, sem pretium sit pretium urna, senectus vitae. Scelerisque fermentum, cursus felis dui suspendisse velit pharetra. Augue et duis cursus maecenas eget quam lectus. Accumsan vitae nascetur pharetra rhoncus praesent dictum risus suspendisse.</p>
`,
fields: {
Phone: '(555) 123-4567',
Email: '[email protected]',
Title: 'Senior Front-End Developer',
Team: 'Product Development',
Location: 'San Francisco',
Sits: 'Oasis, 4th floor',
Salary: '$145,000',
Birthday: 'June 8, 1990',
},
}
const directory = {
A: [
{
id: 1,
name: 'Leslie Abbott',
role: 'Co-Founder / CEO',
imageUrl:
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 2,
name: 'Hector Adams',
role: 'VP, Marketing',
imageUrl:
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 3,
name: 'Blake Alexander',
role: 'Account Coordinator',
imageUrl:
'https://images.unsplash.com/photo-1520785643438-5bf77931f493?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 4,
name: 'Fabricio Andrews',
role: 'Senior Art Director',
imageUrl:
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
B: [
{
id: 5,
name: 'Angela Beaver',
role: 'Chief Strategy Officer',
imageUrl:
'https://images.unsplash.com/photo-1501031170107-cfd33f0cbdcc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 6,
name: 'Yvette Blanchard',
role: 'Studio Artist',
imageUrl:
'https://images.unsplash.com/photo-1506980595904-70325b7fdd90?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 7,
name: 'Lawrence Brooks',
role: 'Content Specialist',
imageUrl:
'https://images.unsplash.com/photo-1513910367299-bce8d8a0ebf6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
C: [
{
id: 8,
name: 'Jeffrey Clark',
role: 'Senior Art Director',
imageUrl:
'https://images.unsplash.com/photo-1517070208541-6ddc4d3efbcb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 9,
name: 'Kathryn Cooper',
role: 'Associate Creative Director',
imageUrl:
'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
E: [
{
id: 10,
name: 'Alicia Edwards',
role: 'Junior Copywriter',
imageUrl:
'https://images.unsplash.com/photo-1509783236416-c9ad59bae472?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 11,
name: 'Benjamin Emerson',
role: 'Director, Print Operations',
imageUrl:
'https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 12,
name: 'Jillian Erics',
role: 'Designer',
imageUrl:
'https://images.unsplash.com/photo-1504703395950-b89145a5425b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 13,
name: 'Chelsea Evans',
role: 'Human Resources Manager',
imageUrl:
'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
G: [
{
id: 14,
name: 'Michael Gillard',
role: 'Co-Founder / CTO',
imageUrl:
'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 15,
name: 'Dries Giuessepe',
role: 'Manager, Business Relations',
imageUrl:
'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
M: [
{
id: 16,
name: 'Jenny Harrison',
role: 'Studio Artist',
imageUrl:
'https://images.unsplash.com/photo-1507101105822-7472b28e22ac?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 17,
name: 'Lindsay Hatley',
role: 'Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 18,
name: 'Anna Hill',
role: 'Partner, Creative',
imageUrl:
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
S: [
{
id: 19,
name: 'Courtney Samuels',
role: 'Designer',
imageUrl:
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 20,
name: 'Tom Simpson',
role: 'Director, Product Development',
imageUrl:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
T: [
{
id: 21,
name: 'Floyd Thompson',
role: 'Principal Designer',
imageUrl:
'https://images.unsplash.com/photo-1463453091185-61582044d556?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 22,
name: 'Leonard Timmons',
role: 'Senior Designer',
imageUrl:
'https://images.unsplash.com/photo-1519345182560-3f2917c472ef?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 23,
name: 'Whitney Trudeau',
role: 'Copywriter',
imageUrl:
'https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
W: [
{
id: 24,
name: 'Kristin Watson',
role: 'VP, Human Resources',
imageUrl:
'https://images.unsplash.com/photo-1500917293891-ef795e70e1f6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 25,
name: 'Emily Wilson',
role: 'VP, User Experience',
imageUrl:
'https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
Y: [
{
id: 26,
name: 'Emma Young',
role: 'Senior Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1505840717430-882ce147ef2d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
}
const team = [
{
name: 'Leslie Alexander',
handle: 'lesliealexander',
role: 'Co-Founder / CEO',
imageUrl:
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Michael Foster',
handle: 'michaelfoster',
role: 'Co-Founder / CTO',
imageUrl:
'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Dries Vincent',
handle: 'driesvincent',
role: 'Manager, Business Relations',
imageUrl:
'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Lindsay Walton',
handle: 'lindsaywalton',
role: 'Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Example() {
const [sidebarOpen, setSidebarOpen] = useState(false)
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full bg-white">
<body class="h-full overflow-hidden">
```
*/}
<div className="h-full flex">
<Transition.Root show={sidebarOpen} as={Fragment}>
<Dialog as="div" className="fixed inset-0 flex z-40 lg:hidden" onClose={setSidebarOpen}>
<Transition.Child
as={Fragment}
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-600 bg-opacity-75" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition ease-in-out duration-300 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>
<div className="relative flex-1 flex flex-col max-w-xs w-full bg-white focus:outline-none">
<Transition.Child
as={Fragment}
enter="ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute top-0 right-0 -mr-12 pt-2">
<button
type="button"
className="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
onClick={() => setSidebarOpen(false)}
>
<span className="sr-only">Close sidebar</span>
<XIcon className="h-6 w-6 text-white" aria-hidden="true" />
</button>
</div>
</Transition.Child>
<div className="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
<div className="flex-shrink-0 flex items-center px-4">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-pink-500-mark-gray-900-text.svg"
alt="Workflow"
/>
</div>
<nav aria-label="Sidebar" className="mt-5">
<div className="px-2 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-100 text-gray-900'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900',
'group flex items-center px-2 py-2 text-base font-medium rounded-md'
)}
aria-current={item.current ? 'page' : undefined}
>
<item.icon
className={classNames(
item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500',
'mr-4 h-6 w-6'
)}
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
<hr className="border-t border-gray-200 my-5" aria-hidden="true" />
<div className="px-2 space-y-1">
{secondaryNavigation.map((item) => (
<a
key={item.name}
href={item.href}
className="text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-base font-medium rounded-md"
>
<item.icon
className="text-gray-400 group-hover:text-gray-500 mr-4 flex-shrink-0 h-6 w-6"
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 group block">
<div className="flex items-center">
<div>
<img className="inline-block h-10 w-10 rounded-full" src={user.imageUrl} alt="" />
</div>
<div className="ml-3">
<p className="text-base font-medium text-gray-700 group-hover:text-gray-900">{user.name}</p>
<p className="text-sm font-medium text-gray-500 group-hover:text-gray-700">View profile</p>
</div>
</div>
</a>
</div>
</div>
</Transition.Child>
<div className="flex-shrink-0 w-14" aria-hidden="true">
{/* Force sidebar to shrink to fit close icon */}
</div>
</Dialog>
</Transition.Root>
{/* Static sidebar for desktop */}
<div className="hidden lg:flex lg:flex-shrink-0">
<div className="flex flex-col w-64">
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex-1 flex flex-col min-h-0 border-r border-gray-200 bg-gray-100">
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-pink-500-mark-gray-900-text.svg"
alt="Workflow"
/>
</div>
<nav className="mt-5 flex-1" aria-label="Sidebar">
<div className="px-2 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-200 text-gray-900'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900',
'group flex items-center px-2 py-2 text-sm font-medium rounded-md'
)}
aria-current={item.current ? 'page' : undefined}
>
<item.icon
className={classNames(
item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500',
'mr-3 flex-shrink-0 h-6 w-6'
)}
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
<hr className="border-t border-gray-200 my-5" aria-hidden="true" />
<div className="flex-1 px-2 space-y-1">
{secondaryNavigation.map((item) => (
<a
key={item.name}
href={item.href}
className="text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md"
>
<item.icon
className="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 w-full group block">
<div className="flex items-center">
<div>
<img className="inline-block h-9 w-9 rounded-full" src={user.imageUrl} alt="" />
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-700 group-hover:text-gray-900">{user.name}</p>
<p className="text-xs font-medium text-gray-500 group-hover:text-gray-700">View profile</p>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<div className="flex flex-col min-w-0 flex-1 overflow-hidden">
<div className="lg:hidden">
<div className="flex items-center justify-between bg-gray-50 border-b border-gray-200 px-4 py-1.5">
<div>
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-mark-pink-500.svg"
alt="Workflow"
/>
</div>
<div>
<button
type="button"
className="-mr-3 h-12 w-12 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-pink-600"
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<MenuIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div className="flex-1 relative z-0 flex overflow-hidden">
<main className="flex-1 relative z-0 overflow-y-auto focus:outline-none xl:order-last">
{/* Breadcrumb */}
<nav className="flex items-start px-4 py-3 sm:px-6 lg:px-8 xl:hidden" aria-label="Breadcrumb">
<a href="#" className="inline-flex items-center space-x-3 text-sm font-medium text-gray-900">
<ChevronLeftIcon className="-ml-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Directory</span>
</a>
</nav>
<article>
{/* Profile header */}
<div>
<div>
<img className="h-32 w-full object-cover lg:h-48" src={profile.coverImageUrl} alt="" />
</div>
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="-mt-12 sm:-mt-16 sm:flex sm:items-end sm:space-x-5">
<div className="flex">
<img
className="h-24 w-24 rounded-full ring-4 ring-white sm:h-32 sm:w-32"
src={profile.imageUrl}
alt=""
/>
</div>
<div className="mt-6 sm:flex-1 sm:min-w-0 sm:flex sm:items-center sm:justify-end sm:space-x-6 sm:pb-1">
<div className="sm:hidden 2xl:block mt-6 min-w-0 flex-1">
<h1 className="text-2xl font-bold text-gray-900 truncate">{profile.name}</h1>
</div>
<div className="mt-6 flex flex-col justify-stretch space-y-3 sm:flex-row sm:space-y-0 sm:space-x-4">
<button
type="button"
className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<MailIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Message</span>
</button>
<button
type="button"
className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<PhoneIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Call</span>
</button>
</div>
</div>
</div>
<div className="hidden sm:block 2xl:hidden mt-6 min-w-0 flex-1">
<h1 className="text-2xl font-bold text-gray-900 truncate">{profile.name}</h1>
</div>
</div>
</div>
{/* Tabs */}
<div className="mt-6 sm:mt-2 2xl:mt-5">
<div className="border-b border-gray-200">
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.current
? 'border-pink-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
'whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm'
)}
aria-current={tab.current ? 'page' : undefined}
>
{tab.name}
</a>
))}
</nav>
</div>
</div>
</div>
{/* Description list */}
<div className="mt-6 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<dl className="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
{Object.keys(profile.fields).map((field) => (
<div key={field} className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">{field}</dt>
<dd className="mt-1 text-sm text-gray-900">{profile.fields[field]}</dd>
</div>
))}
<div className="sm:col-span-2">
<dt className="text-sm font-medium text-gray-500">About</dt>
<dd
className="mt-1 max-w-prose text-sm text-gray-900 space-y-5"
dangerouslySetInnerHTML={{ __html: profile.about }}
/>
</div>
</dl>
</div>
{/* Team member list */}
<div className="mt-8 max-w-5xl mx-auto px-4 pb-12 sm:px-6 lg:px-8">
<h2 className="text-sm font-medium text-gray-500">Team members</h2>
<div className="mt-1 grid grid-cols-1 gap-4 sm:grid-cols-2">
{team.map((person) => (
<div
key={person.handle}
className="relative rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm flex items-center space-x-3 hover:border-gray-400 focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-pink-500"
>
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src={person.imageUrl} alt="" />
</div>
<div className="flex-1 min-w-0">
<a href="#" className="focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
<p className="text-sm font-medium text-gray-900">{person.name}</p>
<p className="text-sm text-gray-500 truncate">{person.role}</p>
</a>
</div>
</div>
))}
</div>
</div>
</article>
</main>
<aside className="hidden xl:order-first xl:flex xl:flex-col flex-shrink-0 w-96 border-r border-gray-200">
<div className="px-6 pt-6 pb-4">
<h2 className="text-lg font-medium text-gray-900">Directory</h2>
<p className="mt-1 text-sm text-gray-600">Search directory of 3,018 employees</p>
<form className="mt-6 flex space-x-4" action="#">
<div className="flex-1 min-w-0">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative rounded-md shadow-sm">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
<input
type="search"
name="search"
id="search"
className="focus:ring-pink-500 focus:border-pink-500 block w-full pl-10 sm:text-sm border-gray-300 rounded-md"
placeholder="Search"
/>
</div>
</div>
<button
type="submit"
className="inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<FilterIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
<span className="sr-only">Search</span>
</button>
</form>
</div>
{/* Directory list */}
<nav className="flex-1 min-h-0 overflow-y-auto" aria-label="Directory">
{Object.keys(directory).map((letter) => (
<div key={letter} className="relative">
<div className="z-10 sticky top-0 border-t border-b border-gray-200 bg-gray-50 px-6 py-1 text-sm font-medium text-gray-500">
<h3>{letter}</h3>
</div>
<ul role="list" className="relative z-0 divide-y divide-gray-200">
{directory[letter].map((person) => (
<li key={person.id}>
<div className="relative px-6 py-5 flex items-center space-x-3 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-pink-500">
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src={person.imageUrl} alt="" />
</div>
<div className="flex-1 min-w-0">
<a href="#" className="focus:outline-none">
{/* Extend touch target to entire panel */}
<span className="absolute inset-0" aria-hidden="true" />
<p className="text-sm font-medium text-gray-900">{person.name}</p>
<p className="text-sm text-gray-500 truncate">{person.role}</p>
</a>
</div>
</div>
</li>
))}
</ul>
</div>
))}
</nav>
</aside>
</div>
</div>
</div>
</>
)
}
app/route/dashboard/users/$userId.tsx
rendering the profile details section of this Multi-column directory screenapp/route/dashboard/users/$userId.tsx
file to load the User data then show the data into the screenCall to action to purchase the course.
Dashboard page should render this Single price with details call to action.
/dashboard/purchase
route for me to start the transaction./* This example requires Tailwind CSS v2.0+ */
import { CheckCircleIcon } from '@heroicons/react/solid'
const includedFeatures = [
'Private forum access',
'Member resources',
'Entry to annual conference',
'Official member t-shirt',
]
export default function Example() {
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">Simple no-tricks pricing</h2>
<p className="mt-4 text-xl text-gray-600">
If you're not satisfied, contact us within the first 14 days and we'll send you a full refund.
</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">Lifetime Membership</h3>
<p className="mt-6 text-base text-gray-500">
Lorem ipsum dolor sit amet consect etur adipisicing elit. Itaque amet indis perferendis blanditiis
repellendus etur quidem assumenda.
</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">
What's included
</h4>
<div className="flex-1 border-t-2 border-gray-200" />
</div>
<ul role="list" 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">Pay once, own it forever</p>
<div className="mt-4 flex items-center justify-center text-5xl font-extrabold text-gray-900">
<span>$349</span>
<span className="ml-3 text-xl font-medium text-gray-500">USD</span>
</div>
<p className="mt-4 text-sm">
<a href="#" className="font-medium text-gray-500 underline">
Learn about our membership policy
</a>
</p>
<div className="mt-6">
<div className="rounded-md shadow">
<a
href="#"
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"
>
Get Access
</a>
</div>
</div>
<div className="mt-4 text-sm">
<a href="#" className="font-medium text-gray-900">
Get a free sample <span className="font-normal text-gray-500">(20MB)</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
/dashboard/purchase
route/dashboard/purchase
route pagee2e/cta.spec.ts
file/dashboard/index.tsx
page and put it in the /dashboard/settings.tsx
route instead.Btw, I've merged my current branch with the latest upstream branch last night. But, after I run npm run setup
, I got a warning like this
After that, my package-lock.json
has a lot of changes like this. Currently, I'm not commit these changes.
Is this OK mas @zainfathoni ?
NodeJS and NPM versions I'm using
❯ node -v
v16.13.1
❯ npm -v
8.1.2
Originally posted by @guntoroyk in #45 (comment)
Thanks to our fellow contributors, we managed to work for multiple streams in parallel.
Unfortunately, our end-to-end test cannot test the upstream works without the downstream elements implemented, e.g. it couldn't test the database schema and operations without the form implementation.
Therefore, It's time to introduce a proper unit and integration tests infrastructure.
You can refer to this PR as an example: kawalcovid19/wargabantuwarga.com#358.
jest
, testing-library
, and @jackfranklin/test-data-bot
libraries
test:watch
for running tests while watching changed filestest:coverage
for running all tests and gather the test coveragetest:debug
for debugging unit testsTo ease our contributors' life, we need to continuously improve our developer experience in this project.
docs
directoryIt is concerning that we have another testing fixture (other than the auth.json
file) that we need to update whenever we reset the test.db
using this command:
DATABASE_URL="file:./test.db" npx prisma migrate reset
After running that command above, we will get these errors when we run this command:
npm run test:e2e:run
Originally posted by @zainfathoni in #97 (comment)
Ideally, we shouldn't need to track the test.db
file into the Git repository. So whenever we run the npx prisma migrate reset
command (which will seed the database using seed.ts
file), we should be able to have a successful e2e tests running without changing any fixtures manually.
DATABASE_URL="file:./test.db" npx prisma migrate reset
command,test.db
file is updated,npm run test:e2e:run
command,seed.ts
file accordingly:
npm run test:e2e:run
command,test.db
file and the updated fixtures to make the tests successful in GitHub Actions, too.DATABASE_URL="file:./test.db" npx prisma migrate reset
command,test.db
file is updated,npm run test:e2e:run
command,@jackfranklin/test-data-bot
in seed.ts
to fake random datatest.db
from the Git repositoryExtract UI fragments into reusable components and style them accordingly.
Image
component backed by Cloudinary for image optimizationsUse NanoID to make the URLs shorter.
TBD
Build pages for the /dashboard/profile/*
routes to facilitate users viewing and editing their profiles.
The /profile
route should show the current user's data based on this Multi-column directory screen. For this route, we only use the profile details section without the list of the users.
/dashboard/profile
route,/dashboard/profile/edit
route./dashboard/profile/edit
route,/dashboard/settings
route,/dashboard/profile
route with my updated data.import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import {
CalendarIcon,
CogIcon,
HomeIcon,
MapIcon,
MenuIcon,
SearchCircleIcon,
SpeakerphoneIcon,
UserGroupIcon,
ViewGridAddIcon,
XIcon,
} from '@heroicons/react/outline'
import { ChevronLeftIcon, FilterIcon, MailIcon, PhoneIcon, SearchIcon } from '@heroicons/react/solid'
const user = {
name: 'Tom Cook',
imageUrl:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
}
const navigation = [
{ name: 'Dashboard', href: '#', icon: HomeIcon, current: false },
{ name: 'Calendar', href: '#', icon: CalendarIcon, current: false },
{ name: 'Teams', href: '#', icon: UserGroupIcon, current: false },
{ name: 'Directory', href: '#', icon: SearchCircleIcon, current: true },
{ name: 'Announcements', href: '#', icon: SpeakerphoneIcon, current: false },
{ name: 'Office Map', href: '#', icon: MapIcon, current: false },
]
const secondaryNavigation = [
{ name: 'Apps', href: '#', icon: ViewGridAddIcon },
{ name: 'Settings', href: '#', icon: CogIcon },
]
const tabs = [
{ name: 'Profile', href: '#', current: true },
{ name: 'Calendar', href: '#', current: false },
{ name: 'Recognition', href: '#', current: false },
]
const profile = {
name: 'Ricardo Cooper',
imageUrl:
'https://images.unsplash.com/photo-1463453091185-61582044d556?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=1024&h=1024&q=80',
coverImageUrl:
'https://images.unsplash.com/photo-1444628838545-ac4016a5418a?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80',
about: `
<p>Tincidunt quam neque in cursus viverra orci, dapibus nec tristique. Nullam ut sit dolor consectetur urna, dui cras nec sed. Cursus risus congue arcu aenean posuere aliquam.</p>
<p>Et vivamus lorem pulvinar nascetur non. Pulvinar a sed platea rhoncus ac mauris amet. Urna, sem pretium sit pretium urna, senectus vitae. Scelerisque fermentum, cursus felis dui suspendisse velit pharetra. Augue et duis cursus maecenas eget quam lectus. Accumsan vitae nascetur pharetra rhoncus praesent dictum risus suspendisse.</p>
`,
fields: {
Phone: '(555) 123-4567',
Email: '[email protected]',
Title: 'Senior Front-End Developer',
Team: 'Product Development',
Location: 'San Francisco',
Sits: 'Oasis, 4th floor',
Salary: '$145,000',
Birthday: 'June 8, 1990',
},
}
const directory = {
A: [
{
id: 1,
name: 'Leslie Abbott',
role: 'Co-Founder / CEO',
imageUrl:
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 2,
name: 'Hector Adams',
role: 'VP, Marketing',
imageUrl:
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 3,
name: 'Blake Alexander',
role: 'Account Coordinator',
imageUrl:
'https://images.unsplash.com/photo-1520785643438-5bf77931f493?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 4,
name: 'Fabricio Andrews',
role: 'Senior Art Director',
imageUrl:
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
B: [
{
id: 5,
name: 'Angela Beaver',
role: 'Chief Strategy Officer',
imageUrl:
'https://images.unsplash.com/photo-1501031170107-cfd33f0cbdcc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 6,
name: 'Yvette Blanchard',
role: 'Studio Artist',
imageUrl:
'https://images.unsplash.com/photo-1506980595904-70325b7fdd90?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 7,
name: 'Lawrence Brooks',
role: 'Content Specialist',
imageUrl:
'https://images.unsplash.com/photo-1513910367299-bce8d8a0ebf6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
C: [
{
id: 8,
name: 'Jeffrey Clark',
role: 'Senior Art Director',
imageUrl:
'https://images.unsplash.com/photo-1517070208541-6ddc4d3efbcb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 9,
name: 'Kathryn Cooper',
role: 'Associate Creative Director',
imageUrl:
'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
E: [
{
id: 10,
name: 'Alicia Edwards',
role: 'Junior Copywriter',
imageUrl:
'https://images.unsplash.com/photo-1509783236416-c9ad59bae472?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 11,
name: 'Benjamin Emerson',
role: 'Director, Print Operations',
imageUrl:
'https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 12,
name: 'Jillian Erics',
role: 'Designer',
imageUrl:
'https://images.unsplash.com/photo-1504703395950-b89145a5425b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 13,
name: 'Chelsea Evans',
role: 'Human Resources Manager',
imageUrl:
'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
G: [
{
id: 14,
name: 'Michael Gillard',
role: 'Co-Founder / CTO',
imageUrl:
'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 15,
name: 'Dries Giuessepe',
role: 'Manager, Business Relations',
imageUrl:
'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
M: [
{
id: 16,
name: 'Jenny Harrison',
role: 'Studio Artist',
imageUrl:
'https://images.unsplash.com/photo-1507101105822-7472b28e22ac?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 17,
name: 'Lindsay Hatley',
role: 'Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 18,
name: 'Anna Hill',
role: 'Partner, Creative',
imageUrl:
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
S: [
{
id: 19,
name: 'Courtney Samuels',
role: 'Designer',
imageUrl:
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 20,
name: 'Tom Simpson',
role: 'Director, Product Development',
imageUrl:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
T: [
{
id: 21,
name: 'Floyd Thompson',
role: 'Principal Designer',
imageUrl:
'https://images.unsplash.com/photo-1463453091185-61582044d556?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 22,
name: 'Leonard Timmons',
role: 'Senior Designer',
imageUrl:
'https://images.unsplash.com/photo-1519345182560-3f2917c472ef?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 23,
name: 'Whitney Trudeau',
role: 'Copywriter',
imageUrl:
'https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
W: [
{
id: 24,
name: 'Kristin Watson',
role: 'VP, Human Resources',
imageUrl:
'https://images.unsplash.com/photo-1500917293891-ef795e70e1f6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 25,
name: 'Emily Wilson',
role: 'VP, User Experience',
imageUrl:
'https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
Y: [
{
id: 26,
name: 'Emma Young',
role: 'Senior Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1505840717430-882ce147ef2d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
}
const team = [
{
name: 'Leslie Alexander',
handle: 'lesliealexander',
role: 'Co-Founder / CEO',
imageUrl:
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Michael Foster',
handle: 'michaelfoster',
role: 'Co-Founder / CTO',
imageUrl:
'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Dries Vincent',
handle: 'driesvincent',
role: 'Manager, Business Relations',
imageUrl:
'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Lindsay Walton',
handle: 'lindsaywalton',
role: 'Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Example() {
const [sidebarOpen, setSidebarOpen] = useState(false)
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full bg-white">
<body class="h-full overflow-hidden">
```
*/}
<div className="h-full flex">
<Transition.Root show={sidebarOpen} as={Fragment}>
<Dialog as="div" className="fixed inset-0 flex z-40 lg:hidden" onClose={setSidebarOpen}>
<Transition.Child
as={Fragment}
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-600 bg-opacity-75" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition ease-in-out duration-300 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>
<div className="relative flex-1 flex flex-col max-w-xs w-full bg-white focus:outline-none">
<Transition.Child
as={Fragment}
enter="ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute top-0 right-0 -mr-12 pt-2">
<button
type="button"
className="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
onClick={() => setSidebarOpen(false)}
>
<span className="sr-only">Close sidebar</span>
<XIcon className="h-6 w-6 text-white" aria-hidden="true" />
</button>
</div>
</Transition.Child>
<div className="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
<div className="flex-shrink-0 flex items-center px-4">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-pink-500-mark-gray-900-text.svg"
alt="Workflow"
/>
</div>
<nav aria-label="Sidebar" className="mt-5">
<div className="px-2 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-100 text-gray-900'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900',
'group flex items-center px-2 py-2 text-base font-medium rounded-md'
)}
aria-current={item.current ? 'page' : undefined}
>
<item.icon
className={classNames(
item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500',
'mr-4 h-6 w-6'
)}
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
<hr className="border-t border-gray-200 my-5" aria-hidden="true" />
<div className="px-2 space-y-1">
{secondaryNavigation.map((item) => (
<a
key={item.name}
href={item.href}
className="text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-base font-medium rounded-md"
>
<item.icon
className="text-gray-400 group-hover:text-gray-500 mr-4 flex-shrink-0 h-6 w-6"
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 group block">
<div className="flex items-center">
<div>
<img className="inline-block h-10 w-10 rounded-full" src={user.imageUrl} alt="" />
</div>
<div className="ml-3">
<p className="text-base font-medium text-gray-700 group-hover:text-gray-900">{user.name}</p>
<p className="text-sm font-medium text-gray-500 group-hover:text-gray-700">View profile</p>
</div>
</div>
</a>
</div>
</div>
</Transition.Child>
<div className="flex-shrink-0 w-14" aria-hidden="true">
{/* Force sidebar to shrink to fit close icon */}
</div>
</Dialog>
</Transition.Root>
{/* Static sidebar for desktop */}
<div className="hidden lg:flex lg:flex-shrink-0">
<div className="flex flex-col w-64">
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex-1 flex flex-col min-h-0 border-r border-gray-200 bg-gray-100">
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-pink-500-mark-gray-900-text.svg"
alt="Workflow"
/>
</div>
<nav className="mt-5 flex-1" aria-label="Sidebar">
<div className="px-2 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-200 text-gray-900'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900',
'group flex items-center px-2 py-2 text-sm font-medium rounded-md'
)}
aria-current={item.current ? 'page' : undefined}
>
<item.icon
className={classNames(
item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500',
'mr-3 flex-shrink-0 h-6 w-6'
)}
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
<hr className="border-t border-gray-200 my-5" aria-hidden="true" />
<div className="flex-1 px-2 space-y-1">
{secondaryNavigation.map((item) => (
<a
key={item.name}
href={item.href}
className="text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md"
>
<item.icon
className="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 w-full group block">
<div className="flex items-center">
<div>
<img className="inline-block h-9 w-9 rounded-full" src={user.imageUrl} alt="" />
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-700 group-hover:text-gray-900">{user.name}</p>
<p className="text-xs font-medium text-gray-500 group-hover:text-gray-700">View profile</p>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<div className="flex flex-col min-w-0 flex-1 overflow-hidden">
<div className="lg:hidden">
<div className="flex items-center justify-between bg-gray-50 border-b border-gray-200 px-4 py-1.5">
<div>
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-mark-pink-500.svg"
alt="Workflow"
/>
</div>
<div>
<button
type="button"
className="-mr-3 h-12 w-12 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-pink-600"
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<MenuIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div className="flex-1 relative z-0 flex overflow-hidden">
<main className="flex-1 relative z-0 overflow-y-auto focus:outline-none xl:order-last">
{/* Breadcrumb */}
<nav className="flex items-start px-4 py-3 sm:px-6 lg:px-8 xl:hidden" aria-label="Breadcrumb">
<a href="#" className="inline-flex items-center space-x-3 text-sm font-medium text-gray-900">
<ChevronLeftIcon className="-ml-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Directory</span>
</a>
</nav>
<article>
{/* Profile header */}
<div>
<div>
<img className="h-32 w-full object-cover lg:h-48" src={profile.coverImageUrl} alt="" />
</div>
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="-mt-12 sm:-mt-16 sm:flex sm:items-end sm:space-x-5">
<div className="flex">
<img
className="h-24 w-24 rounded-full ring-4 ring-white sm:h-32 sm:w-32"
src={profile.imageUrl}
alt=""
/>
</div>
<div className="mt-6 sm:flex-1 sm:min-w-0 sm:flex sm:items-center sm:justify-end sm:space-x-6 sm:pb-1">
<div className="sm:hidden 2xl:block mt-6 min-w-0 flex-1">
<h1 className="text-2xl font-bold text-gray-900 truncate">{profile.name}</h1>
</div>
<div className="mt-6 flex flex-col justify-stretch space-y-3 sm:flex-row sm:space-y-0 sm:space-x-4">
<button
type="button"
className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<MailIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Message</span>
</button>
<button
type="button"
className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<PhoneIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Call</span>
</button>
</div>
</div>
</div>
<div className="hidden sm:block 2xl:hidden mt-6 min-w-0 flex-1">
<h1 className="text-2xl font-bold text-gray-900 truncate">{profile.name}</h1>
</div>
</div>
</div>
{/* Tabs */}
<div className="mt-6 sm:mt-2 2xl:mt-5">
<div className="border-b border-gray-200">
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.current
? 'border-pink-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
'whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm'
)}
aria-current={tab.current ? 'page' : undefined}
>
{tab.name}
</a>
))}
</nav>
</div>
</div>
</div>
{/* Description list */}
<div className="mt-6 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<dl className="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
{Object.keys(profile.fields).map((field) => (
<div key={field} className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">{field}</dt>
<dd className="mt-1 text-sm text-gray-900">{profile.fields[field]}</dd>
</div>
))}
<div className="sm:col-span-2">
<dt className="text-sm font-medium text-gray-500">About</dt>
<dd
className="mt-1 max-w-prose text-sm text-gray-900 space-y-5"
dangerouslySetInnerHTML={{ __html: profile.about }}
/>
</div>
</dl>
</div>
{/* Team member list */}
<div className="mt-8 max-w-5xl mx-auto px-4 pb-12 sm:px-6 lg:px-8">
<h2 className="text-sm font-medium text-gray-500">Team members</h2>
<div className="mt-1 grid grid-cols-1 gap-4 sm:grid-cols-2">
{team.map((person) => (
<div
key={person.handle}
className="relative rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm flex items-center space-x-3 hover:border-gray-400 focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-pink-500"
>
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src={person.imageUrl} alt="" />
</div>
<div className="flex-1 min-w-0">
<a href="#" className="focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
<p className="text-sm font-medium text-gray-900">{person.name}</p>
<p className="text-sm text-gray-500 truncate">{person.role}</p>
</a>
</div>
</div>
))}
</div>
</div>
</article>
</main>
<aside className="hidden xl:order-first xl:flex xl:flex-col flex-shrink-0 w-96 border-r border-gray-200">
<div className="px-6 pt-6 pb-4">
<h2 className="text-lg font-medium text-gray-900">Directory</h2>
<p className="mt-1 text-sm text-gray-600">Search directory of 3,018 employees</p>
<form className="mt-6 flex space-x-4" action="#">
<div className="flex-1 min-w-0">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative rounded-md shadow-sm">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
<input
type="search"
name="search"
id="search"
className="focus:ring-pink-500 focus:border-pink-500 block w-full pl-10 sm:text-sm border-gray-300 rounded-md"
placeholder="Search"
/>
</div>
</div>
<button
type="submit"
className="inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<FilterIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
<span className="sr-only">Search</span>
</button>
</form>
</div>
{/* Directory list */}
<nav className="flex-1 min-h-0 overflow-y-auto" aria-label="Directory">
{Object.keys(directory).map((letter) => (
<div key={letter} className="relative">
<div className="z-10 sticky top-0 border-t border-b border-gray-200 bg-gray-50 px-6 py-1 text-sm font-medium text-gray-500">
<h3>{letter}</h3>
</div>
<ul role="list" className="relative z-0 divide-y divide-gray-200">
{directory[letter].map((person) => (
<li key={person.id}>
<div className="relative px-6 py-5 flex items-center space-x-3 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-pink-500">
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src={person.imageUrl} alt="" />
</div>
<div className="flex-1 min-w-0">
<a href="#" className="focus:outline-none">
{/* Extend touch target to entire panel */}
<span className="absolute inset-0" aria-hidden="true" />
<p className="text-sm font-medium text-gray-900">{person.name}</p>
<p className="text-sm text-gray-500 truncate">{person.role}</p>
</a>
</div>
</div>
</li>
))}
</ul>
</div>
))}
</nav>
</aside>
</div>
</div>
</div>
</>
)
}
app/route/dashboard/profile.tsx
file as a route shell rendering nothing other than Remix's Outlet componentapp/route/dashboard/profile/index.tsx
file rendering the profile details section of this Multi-column directory screenmain
branch.Implement the Transactions List page to show all transactions related to the Author. We can do it in parallel with #84.
The /dashboard/transactions
route should load all the transactions related to the Author and show them as a list. Please compose the UI based on this With page heading and stacked list screen.
/dashboard/transactions
route,/dashboard/transactions/$transactionId
route to see the details of that transaction.Use the denormalized authorId
field to get all the Author's transactions.
import { Fragment, useState } from 'react'
import { Disclosure, Listbox, Menu, Transition } from '@headlessui/react'
import {
ArrowNarrowLeftIcon,
ArrowNarrowRightIcon,
BriefcaseIcon,
CalendarIcon,
CheckCircleIcon,
CheckIcon,
ChevronDownIcon,
ChevronRightIcon,
CurrencyDollarIcon,
LinkIcon,
LocationMarkerIcon,
MailIcon,
PencilIcon,
SearchIcon,
} from '@heroicons/react/solid'
import { BellIcon, MenuIcon, XIcon } from '@heroicons/react/outline'
const user = {
name: 'Whitney Francis',
email: '[email protected]',
imageUrl:
'https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
}
const navigation = [
{ name: 'Dashboard', href: '#', current: true },
{ name: 'Jobs', href: '#', current: false },
{ name: 'Applicants', href: '#', current: false },
{ name: 'Company', href: '#', current: false },
]
const userNavigation = [
{ name: 'Your Profile', href: '#' },
{ name: 'Settings', href: '#' },
{ name: 'Sign out', href: '#' },
]
const tabs = [
{ name: 'Applied', href: '#', count: '2', current: false },
{ name: 'Phone Screening', href: '#', count: '4', current: false },
{ name: 'Interview', href: '#', count: '6', current: true },
{ name: 'Offer', href: '#', current: false },
{ name: 'Disqualified', href: '#', current: false },
]
const candidates = [
{
name: 'Emily Selman',
email: '[email protected]',
imageUrl:
'https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
applied: 'January 7, 2020',
appliedDatetime: '2020-07-01T15:34:56',
status: 'Completed phone screening',
},
// More candidates...
]
const publishingOptions = [
{ name: 'Published', description: 'This job posting can be viewed by anyone who has the link.', current: true },
{ name: 'Draft', description: 'This job posting will no longer be publicly accessible.', current: false },
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Example() {
const [selected, setSelected] = useState(publishingOptions[0])
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full bg-white">
<body class="h-full">
```
*/}
<div className="min-h-full">
{/* Navbar */}
<Disclosure as="nav" className="bg-gray-50">
{({ open }) => (
<>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="relative h-16 flex items-center justify-between border-b border-gray-200">
<div className="flex items-center">
<div className="flex-shrink-0">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-mark.svg?color=violet&shade=500"
alt="Workflow"
/>
</div>
{/* Links section */}
<div className="hidden lg:block lg:ml-10">
<div className="flex space-x-4">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current ? 'bg-gray-100' : 'hover:text-gray-700',
'px-3 py-2 rounded-md text-sm font-medium text-gray-900'
)}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
))}
</div>
</div>
</div>
<div className="flex-1 px-2 flex justify-center lg:ml-6 lg:justify-end">
{/* Search section */}
<div className="max-w-lg w-full lg:max-w-xs">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative text-gray-400 focus-within:text-gray-500">
<div className="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center">
<SearchIcon className="h-5 w-5" aria-hidden="true" />
</div>
<input
id="search"
className="block w-full bg-white py-2 pl-10 pr-3 border border-gray-300 rounded-md leading-5 text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-purple-500 focus:placeholder-gray-500 sm:text-sm"
placeholder="Search"
type="search"
name="search"
/>
</div>
</div>
</div>
<div className="flex lg:hidden">
{/* Mobile menu button */}
<Disclosure.Button className="bg-gray-50 p-2 inline-flex items-center justify-center rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-purple-500">
<span className="sr-only">Open main menu</span>
{open ? (
<XIcon className="block h-6 w-6" aria-hidden="true" />
) : (
<MenuIcon className="block h-6 w-6" aria-hidden="true" />
)}
</Disclosure.Button>
</div>
{/* Actions section */}
<div className="hidden lg:block lg:ml-4">
<div className="flex items-center">
<button
type="button"
className="bg-gray-50 flex-shrink-0 rounded-full p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-purple-500"
>
<span className="sr-only">View notifications</span>
<BellIcon className="h-6 w-6" aria-hidden="true" />
</button>
{/* Profile dropdown */}
<Menu as="div" className="ml-3 relative flex-shrink-0">
<div>
<Menu.Button className="bg-gray-50 rounded-full flex text-sm text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-purple-500">
<span className="sr-only">Open user menu</span>
<img className="rounded-full h-8 w-8" src={user.imageUrl} alt="" />
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute z-10 right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
{userNavigation.map((item) => (
<Menu.Item key={item.name}>
{({ active }) => (
<a
href={item.href}
className={classNames(
active ? 'bg-gray-100' : '',
'block py-2 px-4 text-sm text-gray-700'
)}
>
{item.name}
</a>
)}
</Menu.Item>
))}
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
</div>
</div>
<Disclosure.Panel className="bg-gray-50 border-b border-gray-200 lg:hidden">
<div className="px-2 pt-2 pb-3 space-y-1">
{navigation.map((item) => (
<Disclosure.Button
key={item.name}
as="a"
href={item.href}
className={classNames(
item.current ? 'bg-gray-100' : 'hover:bg-gray-100',
'block px-3 py-2 rounded-md font-medium text-gray-900'
)}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</Disclosure.Button>
))}
</div>
<div className="pt-4 pb-3 border-t border-gray-200">
<div className="px-5 flex items-center">
<div className="flex-shrink-0">
<img className="rounded-full h-10 w-10" src={user.imageUrl} alt="" />
</div>
<div className="ml-3">
<div className="text-base font-medium text-gray-800">{user.name}</div>
<div className="text-sm font-medium text-gray-500">{user.email}</div>
</div>
<button
type="button"
className="ml-auto bg-gray-50 flex-shrink-0 rounded-full p-1 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-purple-500"
>
<span className="sr-only">View notifications</span>
<BellIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="mt-3 px-2 space-y-1">
{userNavigation.map((item) => (
<Disclosure.Button
key={item.name}
as="a"
href={item.href}
className="block rounded-md py-2 px-3 text-base font-medium text-gray-900 hover:bg-gray-100"
>
{item.name}
</Disclosure.Button>
))}
</div>
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
{/* Page heading */}
<header className="bg-gray-50 py-8">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 xl:flex xl:items-center xl:justify-between">
<div className="flex-1 min-w-0">
<nav className="flex" aria-label="Breadcrumb">
<ol role="list" className="flex items-center space-x-4">
<li>
<div>
<a href="#" className="text-sm font-medium text-gray-500 hover:text-gray-700">
Jobs
</a>
</div>
</li>
<li>
<div className="flex items-center">
<ChevronRightIcon className="flex-shrink-0 h-5 w-5 text-gray-400" aria-hidden="true" />
<a href="#" className="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700">
Engineering
</a>
</div>
</li>
</ol>
</nav>
<h1 className="mt-2 text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">
Back End Developer
</h1>
<div className="mt-1 flex flex-col sm:flex-row sm:flex-wrap sm:mt-0 sm:space-x-8">
<div className="mt-2 flex items-center text-sm text-gray-500">
<BriefcaseIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" aria-hidden="true" />
Full-time
</div>
<div className="mt-2 flex items-center text-sm text-gray-500">
<LocationMarkerIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" aria-hidden="true" />
Remote
</div>
<div className="mt-2 flex items-center text-sm text-gray-500">
<CurrencyDollarIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" aria-hidden="true" />
$120k – $140k
</div>
<div className="mt-2 flex items-center text-sm text-gray-500">
<CalendarIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" aria-hidden="true" />
Closing on January 9, 2020
</div>
</div>
</div>
<div className="mt-5 flex xl:mt-0 xl:ml-4">
<span className="hidden sm:block">
<button
type="button"
className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-purple-500"
>
<PencilIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
Edit
</button>
</span>
<span className="hidden sm:block ml-3">
<button
type="button"
className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-purple-500"
>
<LinkIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
View
</button>
</span>
<span className="sm:ml-3 relative z-0">
<Listbox value={selected} onChange={setSelected}>
{({ open }) => (
<>
<Listbox.Label className="sr-only">Change published status</Listbox.Label>
<div className="relative">
<div className="inline-flex shadow-sm rounded-md divide-x divide-purple-600">
<div className="relative z-0 inline-flex shadow-sm rounded-md divide-x divide-purple-600">
<div className="relative inline-flex items-center bg-purple-500 py-2 pl-3 pr-4 border border-transparent rounded-l-md shadow-sm text-white">
<CheckIcon className="h-5 w-5" aria-hidden="true" />
<p className="ml-2.5 text-sm font-medium">{selected.name}</p>
</div>
<Listbox.Button className="relative inline-flex items-center bg-purple-500 p-2 rounded-l-none rounded-r-md text-sm font-medium text-white hover:bg-purple-600 focus:outline-none focus:z-10 focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-50 focus:ring-purple-500">
<span className="sr-only">Change published status</span>
<ChevronDownIcon className="h-5 w-5 text-white" aria-hidden="true" />
</Listbox.Button>
</div>
</div>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="origin-top-right absolute left-0 mt-2 -mr-1 w-72 rounded-md shadow-lg overflow-hidden bg-white divide-y divide-gray-200 ring-1 ring-black ring-opacity-5 focus:outline-none sm:left-auto sm:right-0">
{publishingOptions.map((option) => (
<Listbox.Option
key={option.name}
className={({ active }) =>
classNames(
active ? 'text-white bg-purple-500' : 'text-gray-900',
'cursor-default select-none relative p-4 text-sm'
)
}
value={option}
>
{({ selected, active }) => (
<div className="flex flex-col">
<div className="flex justify-between">
<p className={selected ? 'font-semibold' : 'font-normal'}>{option.name}</p>
{selected ? (
<span className={active ? 'text-white' : 'text-purple-500'}>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</div>
<p className={classNames(active ? 'text-purple-200' : 'text-gray-500', 'mt-2')}>
{option.description}
</p>
</div>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
</span>
{/* Dropdown */}
<Menu as="span" className="ml-3 relative sm:hidden">
<Menu.Button className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
More
<ChevronDownIcon className="-mr-1 ml-2 h-5 w-5 text-gray-500" aria-hidden="true" />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute right-0 mt-2 -mr-1 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
<Menu.Item>
{({ active }) => (
<a
href="#"
className={classNames(active ? 'bg-gray-100' : '', 'block px-4 py-2 text-sm text-gray-700')}
>
Edit
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
href="#"
className={classNames(active ? 'bg-gray-100' : '', 'block px-4 py-2 text-sm text-gray-700')}
>
View
</a>
)}
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
</div>
</div>
</header>
<main className="pt-8 pb-16">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="px-4 sm:px-0">
<h2 className="text-lg font-medium text-gray-900">Candidates</h2>
{/* Tabs */}
<div className="sm:hidden">
<label htmlFor="tabs" className="sr-only">
Select a tab
</label>
{/* Use an "onChange" listener to redirect the user to the selected tab URL. */}
<select
id="tabs"
name="tabs"
className="mt-4 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 sm:block">
<div className="border-b border-gray-200">
<nav className="mt-2 -mb-px flex space-x-8" aria-label="Tabs">
{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:text-gray-700 hover:border-gray-200',
'whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm'
)}
>
{tab.name}
{tab.count ? (
<span
className={classNames(
tab.current ? 'bg-purple-100 text-purple-600' : 'bg-gray-100 text-gray-900',
'hidden ml-2 py-0.5 px-2.5 rounded-full text-xs font-medium md:inline-block'
)}
>
{tab.count}
</span>
) : null}
</a>
))}
</nav>
</div>
</div>
</div>
{/* Stacked list */}
<ul role="list" className="mt-5 border-t border-gray-200 divide-y divide-gray-200 sm:mt-0 sm:border-t-0">
{candidates.map((candidate) => (
<li key={candidate.email}>
<a href="#" className="group block">
<div className="flex items-center py-5 px-4 sm:py-6 sm:px-0">
<div className="min-w-0 flex-1 flex items-center">
<div className="flex-shrink-0">
<img
className="h-12 w-12 rounded-full group-hover:opacity-75"
src={candidate.imageUrl}
alt=""
/>
</div>
<div className="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4">
<div>
<p className="text-sm font-medium text-purple-600 truncate">{candidate.name}</p>
<p className="mt-2 flex items-center text-sm text-gray-500">
<MailIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" aria-hidden="true" />
<span className="truncate">{candidate.email}</span>
</p>
</div>
<div className="hidden md:block">
<div>
<p className="text-sm text-gray-900">
Applied on <time dateTime={candidate.appliedDatetime}>{candidate.applied}</time>
</p>
<p className="mt-2 flex items-center text-sm text-gray-500">
<CheckCircleIcon
className="flex-shrink-0 mr-1.5 h-5 w-5 text-green-400"
aria-hidden="true"
/>
{candidate.status}
</p>
</div>
</div>
</div>
</div>
<div>
<ChevronRightIcon
className="h-5 w-5 text-gray-400 group-hover:text-gray-700"
aria-hidden="true"
/>
</div>
</div>
</a>
</li>
))}
</ul>
{/* Pagination */}
<nav
className="border-t border-gray-200 px-4 flex items-center justify-between sm:px-0"
aria-label="Pagination"
>
<div className="-mt-px w-0 flex-1 flex">
<a
href="#"
className="border-t-2 border-transparent pt-4 pr-1 inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-200"
>
<ArrowNarrowLeftIcon className="mr-3 h-5 w-5 text-gray-400" aria-hidden="true" />
Previous
</a>
</div>
<div className="hidden md:-mt-px md:flex">
<a
href="#"
className="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-200 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
>
1
</a>
{/* Current: "border-purple-500 text-purple-600", Default: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-200" */}
<a
href="#"
className="border-purple-500 text-purple-600 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
aria-current="page"
>
2
</a>
<a
href="#"
className="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-200 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
>
3
</a>
<a
href="#"
className="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-200 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
>
4
</a>
<a
href="#"
className="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-200 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
>
5
</a>
<a
href="#"
className="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-200 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
>
6
</a>
</div>
<div className="-mt-px w-0 flex-1 flex justify-end">
<a
href="#"
className="border-t-2 border-transparent pt-4 pl-1 inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-200"
>
Next
<ArrowNarrowRightIcon className="ml-3 h-5 w-5 text-gray-400" aria-hidden="true" />
</a>
</div>
</nav>
</div>
</main>
</div>
</>
)
}
app/route/dashboard/transactions.tsx
file (if not exist) as a route shell rendering nothing other than Remix's Outlet componentapp/route/dashboard/transactions/index.tsx
rendering all the transactionsapp/route/dashboard/transactions.tsx
file to load all the transactions related to the author then show the data into the screenUpdate dashboard/purchase/index.ts file to contains the Two-column in card containing payment instructions
I'm is first time using playwright, when run command for initial setup (npm run setup
) it will getting error like this :
Then, I run command for download supported browsers :
npx playwright install
Also run initial setup again, to make sure e2e test running properly. But, getting error :
Then, I run this command :
npx playwright install-deps
After that, I can successfully run initial setup.
Can run command for initial setup without getting any error.
Should we change npm run setup
command?
From :
"setup": "npm install && prisma migrate reset --force && npm run test:e2e:run"
Into :
"setup": "npm install && prisma migrate reset --force && playwright install && playwright install-deps && npm run test:e2e:run"
But for command
playwright install-deps
I'm still not sure if it should be added or not, because I haven't tested it on other operating system.
Implement the Users List page to show all users subscribing to the Author's courses.
The /dashboard/users
route should load all the users subscribing to the Author's courses and show them as a list. Please compose the UI based on this Multi-column directory screen.
/dashboard/users
route,/dashboard/users/$userId
route to see the details of that user in the content area.import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import {
CalendarIcon,
CogIcon,
HomeIcon,
MapIcon,
MenuIcon,
SearchCircleIcon,
SpeakerphoneIcon,
UserGroupIcon,
ViewGridAddIcon,
XIcon,
} from '@heroicons/react/outline'
import { ChevronLeftIcon, FilterIcon, MailIcon, PhoneIcon, SearchIcon } from '@heroicons/react/solid'
const user = {
name: 'Tom Cook',
imageUrl:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
}
const navigation = [
{ name: 'Dashboard', href: '#', icon: HomeIcon, current: false },
{ name: 'Calendar', href: '#', icon: CalendarIcon, current: false },
{ name: 'Teams', href: '#', icon: UserGroupIcon, current: false },
{ name: 'Directory', href: '#', icon: SearchCircleIcon, current: true },
{ name: 'Announcements', href: '#', icon: SpeakerphoneIcon, current: false },
{ name: 'Office Map', href: '#', icon: MapIcon, current: false },
]
const secondaryNavigation = [
{ name: 'Apps', href: '#', icon: ViewGridAddIcon },
{ name: 'Settings', href: '#', icon: CogIcon },
]
const tabs = [
{ name: 'Profile', href: '#', current: true },
{ name: 'Calendar', href: '#', current: false },
{ name: 'Recognition', href: '#', current: false },
]
const profile = {
name: 'Ricardo Cooper',
imageUrl:
'https://images.unsplash.com/photo-1463453091185-61582044d556?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=8&w=1024&h=1024&q=80',
coverImageUrl:
'https://images.unsplash.com/photo-1444628838545-ac4016a5418a?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1950&q=80',
about: `
<p>Tincidunt quam neque in cursus viverra orci, dapibus nec tristique. Nullam ut sit dolor consectetur urna, dui cras nec sed. Cursus risus congue arcu aenean posuere aliquam.</p>
<p>Et vivamus lorem pulvinar nascetur non. Pulvinar a sed platea rhoncus ac mauris amet. Urna, sem pretium sit pretium urna, senectus vitae. Scelerisque fermentum, cursus felis dui suspendisse velit pharetra. Augue et duis cursus maecenas eget quam lectus. Accumsan vitae nascetur pharetra rhoncus praesent dictum risus suspendisse.</p>
`,
fields: {
Phone: '(555) 123-4567',
Email: '[email protected]',
Title: 'Senior Front-End Developer',
Team: 'Product Development',
Location: 'San Francisco',
Sits: 'Oasis, 4th floor',
Salary: '$145,000',
Birthday: 'June 8, 1990',
},
}
const directory = {
A: [
{
id: 1,
name: 'Leslie Abbott',
role: 'Co-Founder / CEO',
imageUrl:
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 2,
name: 'Hector Adams',
role: 'VP, Marketing',
imageUrl:
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 3,
name: 'Blake Alexander',
role: 'Account Coordinator',
imageUrl:
'https://images.unsplash.com/photo-1520785643438-5bf77931f493?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 4,
name: 'Fabricio Andrews',
role: 'Senior Art Director',
imageUrl:
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
B: [
{
id: 5,
name: 'Angela Beaver',
role: 'Chief Strategy Officer',
imageUrl:
'https://images.unsplash.com/photo-1501031170107-cfd33f0cbdcc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 6,
name: 'Yvette Blanchard',
role: 'Studio Artist',
imageUrl:
'https://images.unsplash.com/photo-1506980595904-70325b7fdd90?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 7,
name: 'Lawrence Brooks',
role: 'Content Specialist',
imageUrl:
'https://images.unsplash.com/photo-1513910367299-bce8d8a0ebf6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
C: [
{
id: 8,
name: 'Jeffrey Clark',
role: 'Senior Art Director',
imageUrl:
'https://images.unsplash.com/photo-1517070208541-6ddc4d3efbcb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 9,
name: 'Kathryn Cooper',
role: 'Associate Creative Director',
imageUrl:
'https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
E: [
{
id: 10,
name: 'Alicia Edwards',
role: 'Junior Copywriter',
imageUrl:
'https://images.unsplash.com/photo-1509783236416-c9ad59bae472?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 11,
name: 'Benjamin Emerson',
role: 'Director, Print Operations',
imageUrl:
'https://images.unsplash.com/photo-1531427186611-ecfd6d936c79?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 12,
name: 'Jillian Erics',
role: 'Designer',
imageUrl:
'https://images.unsplash.com/photo-1504703395950-b89145a5425b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 13,
name: 'Chelsea Evans',
role: 'Human Resources Manager',
imageUrl:
'https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
G: [
{
id: 14,
name: 'Michael Gillard',
role: 'Co-Founder / CTO',
imageUrl:
'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 15,
name: 'Dries Giuessepe',
role: 'Manager, Business Relations',
imageUrl:
'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
M: [
{
id: 16,
name: 'Jenny Harrison',
role: 'Studio Artist',
imageUrl:
'https://images.unsplash.com/photo-1507101105822-7472b28e22ac?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 17,
name: 'Lindsay Hatley',
role: 'Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 18,
name: 'Anna Hill',
role: 'Partner, Creative',
imageUrl:
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
S: [
{
id: 19,
name: 'Courtney Samuels',
role: 'Designer',
imageUrl:
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 20,
name: 'Tom Simpson',
role: 'Director, Product Development',
imageUrl:
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
T: [
{
id: 21,
name: 'Floyd Thompson',
role: 'Principal Designer',
imageUrl:
'https://images.unsplash.com/photo-1463453091185-61582044d556?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 22,
name: 'Leonard Timmons',
role: 'Senior Designer',
imageUrl:
'https://images.unsplash.com/photo-1519345182560-3f2917c472ef?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 23,
name: 'Whitney Trudeau',
role: 'Copywriter',
imageUrl:
'https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
W: [
{
id: 24,
name: 'Kristin Watson',
role: 'VP, Human Resources',
imageUrl:
'https://images.unsplash.com/photo-1500917293891-ef795e70e1f6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
id: 25,
name: 'Emily Wilson',
role: 'VP, User Experience',
imageUrl:
'https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
Y: [
{
id: 26,
name: 'Emma Young',
role: 'Senior Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1505840717430-882ce147ef2d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
],
}
const team = [
{
name: 'Leslie Alexander',
handle: 'lesliealexander',
role: 'Co-Founder / CEO',
imageUrl:
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Michael Foster',
handle: 'michaelfoster',
role: 'Co-Founder / CTO',
imageUrl:
'https://images.unsplash.com/photo-1519244703995-f4e0f30006d5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Dries Vincent',
handle: 'driesvincent',
role: 'Manager, Business Relations',
imageUrl:
'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
{
name: 'Lindsay Walton',
handle: 'lindsaywalton',
role: 'Front-end Developer',
imageUrl:
'https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
},
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Example() {
const [sidebarOpen, setSidebarOpen] = useState(false)
return (
<>
{/*
This example requires updating your template:
```
<html class="h-full bg-white">
<body class="h-full overflow-hidden">
```
*/}
<div className="h-full flex">
<Transition.Root show={sidebarOpen} as={Fragment}>
<Dialog as="div" className="fixed inset-0 flex z-40 lg:hidden" onClose={setSidebarOpen}>
<Transition.Child
as={Fragment}
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-600 bg-opacity-75" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition ease-in-out duration-300 transform"
enterFrom="-translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="-translate-x-full"
>
<div className="relative flex-1 flex flex-col max-w-xs w-full bg-white focus:outline-none">
<Transition.Child
as={Fragment}
enter="ease-in-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in-out duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="absolute top-0 right-0 -mr-12 pt-2">
<button
type="button"
className="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
onClick={() => setSidebarOpen(false)}
>
<span className="sr-only">Close sidebar</span>
<XIcon className="h-6 w-6 text-white" aria-hidden="true" />
</button>
</div>
</Transition.Child>
<div className="flex-1 h-0 pt-5 pb-4 overflow-y-auto">
<div className="flex-shrink-0 flex items-center px-4">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-pink-500-mark-gray-900-text.svg"
alt="Workflow"
/>
</div>
<nav aria-label="Sidebar" className="mt-5">
<div className="px-2 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-100 text-gray-900'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900',
'group flex items-center px-2 py-2 text-base font-medium rounded-md'
)}
aria-current={item.current ? 'page' : undefined}
>
<item.icon
className={classNames(
item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500',
'mr-4 h-6 w-6'
)}
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
<hr className="border-t border-gray-200 my-5" aria-hidden="true" />
<div className="px-2 space-y-1">
{secondaryNavigation.map((item) => (
<a
key={item.name}
href={item.href}
className="text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-base font-medium rounded-md"
>
<item.icon
className="text-gray-400 group-hover:text-gray-500 mr-4 flex-shrink-0 h-6 w-6"
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 group block">
<div className="flex items-center">
<div>
<img className="inline-block h-10 w-10 rounded-full" src={user.imageUrl} alt="" />
</div>
<div className="ml-3">
<p className="text-base font-medium text-gray-700 group-hover:text-gray-900">{user.name}</p>
<p className="text-sm font-medium text-gray-500 group-hover:text-gray-700">View profile</p>
</div>
</div>
</a>
</div>
</div>
</Transition.Child>
<div className="flex-shrink-0 w-14" aria-hidden="true">
{/* Force sidebar to shrink to fit close icon */}
</div>
</Dialog>
</Transition.Root>
{/* Static sidebar for desktop */}
<div className="hidden lg:flex lg:flex-shrink-0">
<div className="flex flex-col w-64">
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex-1 flex flex-col min-h-0 border-r border-gray-200 bg-gray-100">
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<div className="flex items-center flex-shrink-0 px-4">
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-logo-pink-500-mark-gray-900-text.svg"
alt="Workflow"
/>
</div>
<nav className="mt-5 flex-1" aria-label="Sidebar">
<div className="px-2 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current
? 'bg-gray-200 text-gray-900'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900',
'group flex items-center px-2 py-2 text-sm font-medium rounded-md'
)}
aria-current={item.current ? 'page' : undefined}
>
<item.icon
className={classNames(
item.current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500',
'mr-3 flex-shrink-0 h-6 w-6'
)}
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
<hr className="border-t border-gray-200 my-5" aria-hidden="true" />
<div className="flex-1 px-2 space-y-1">
{secondaryNavigation.map((item) => (
<a
key={item.name}
href={item.href}
className="text-gray-600 hover:bg-gray-50 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md"
>
<item.icon
className="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
aria-hidden="true"
/>
{item.name}
</a>
))}
</div>
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<a href="#" className="flex-shrink-0 w-full group block">
<div className="flex items-center">
<div>
<img className="inline-block h-9 w-9 rounded-full" src={user.imageUrl} alt="" />
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-700 group-hover:text-gray-900">{user.name}</p>
<p className="text-xs font-medium text-gray-500 group-hover:text-gray-700">View profile</p>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
<div className="flex flex-col min-w-0 flex-1 overflow-hidden">
<div className="lg:hidden">
<div className="flex items-center justify-between bg-gray-50 border-b border-gray-200 px-4 py-1.5">
<div>
<img
className="h-8 w-auto"
src="https://tailwindui.com/img/logos/workflow-mark-pink-500.svg"
alt="Workflow"
/>
</div>
<div>
<button
type="button"
className="-mr-3 h-12 w-12 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-pink-600"
onClick={() => setSidebarOpen(true)}
>
<span className="sr-only">Open sidebar</span>
<MenuIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div className="flex-1 relative z-0 flex overflow-hidden">
<main className="flex-1 relative z-0 overflow-y-auto focus:outline-none xl:order-last">
{/* Breadcrumb */}
<nav className="flex items-start px-4 py-3 sm:px-6 lg:px-8 xl:hidden" aria-label="Breadcrumb">
<a href="#" className="inline-flex items-center space-x-3 text-sm font-medium text-gray-900">
<ChevronLeftIcon className="-ml-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Directory</span>
</a>
</nav>
<article>
{/* Profile header */}
<div>
<div>
<img className="h-32 w-full object-cover lg:h-48" src={profile.coverImageUrl} alt="" />
</div>
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="-mt-12 sm:-mt-16 sm:flex sm:items-end sm:space-x-5">
<div className="flex">
<img
className="h-24 w-24 rounded-full ring-4 ring-white sm:h-32 sm:w-32"
src={profile.imageUrl}
alt=""
/>
</div>
<div className="mt-6 sm:flex-1 sm:min-w-0 sm:flex sm:items-center sm:justify-end sm:space-x-6 sm:pb-1">
<div className="sm:hidden 2xl:block mt-6 min-w-0 flex-1">
<h1 className="text-2xl font-bold text-gray-900 truncate">{profile.name}</h1>
</div>
<div className="mt-6 flex flex-col justify-stretch space-y-3 sm:flex-row sm:space-y-0 sm:space-x-4">
<button
type="button"
className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<MailIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Message</span>
</button>
<button
type="button"
className="inline-flex justify-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<PhoneIcon className="-ml-1 mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
<span>Call</span>
</button>
</div>
</div>
</div>
<div className="hidden sm:block 2xl:hidden mt-6 min-w-0 flex-1">
<h1 className="text-2xl font-bold text-gray-900 truncate">{profile.name}</h1>
</div>
</div>
</div>
{/* Tabs */}
<div className="mt-6 sm:mt-2 2xl:mt-5">
<div className="border-b border-gray-200">
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<a
key={tab.name}
href={tab.href}
className={classNames(
tab.current
? 'border-pink-500 text-gray-900'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
'whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm'
)}
aria-current={tab.current ? 'page' : undefined}
>
{tab.name}
</a>
))}
</nav>
</div>
</div>
</div>
{/* Description list */}
<div className="mt-6 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
<dl className="grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2">
{Object.keys(profile.fields).map((field) => (
<div key={field} className="sm:col-span-1">
<dt className="text-sm font-medium text-gray-500">{field}</dt>
<dd className="mt-1 text-sm text-gray-900">{profile.fields[field]}</dd>
</div>
))}
<div className="sm:col-span-2">
<dt className="text-sm font-medium text-gray-500">About</dt>
<dd
className="mt-1 max-w-prose text-sm text-gray-900 space-y-5"
dangerouslySetInnerHTML={{ __html: profile.about }}
/>
</div>
</dl>
</div>
{/* Team member list */}
<div className="mt-8 max-w-5xl mx-auto px-4 pb-12 sm:px-6 lg:px-8">
<h2 className="text-sm font-medium text-gray-500">Team members</h2>
<div className="mt-1 grid grid-cols-1 gap-4 sm:grid-cols-2">
{team.map((person) => (
<div
key={person.handle}
className="relative rounded-lg border border-gray-300 bg-white px-6 py-5 shadow-sm flex items-center space-x-3 hover:border-gray-400 focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-pink-500"
>
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src={person.imageUrl} alt="" />
</div>
<div className="flex-1 min-w-0">
<a href="#" className="focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
<p className="text-sm font-medium text-gray-900">{person.name}</p>
<p className="text-sm text-gray-500 truncate">{person.role}</p>
</a>
</div>
</div>
))}
</div>
</div>
</article>
</main>
<aside className="hidden xl:order-first xl:flex xl:flex-col flex-shrink-0 w-96 border-r border-gray-200">
<div className="px-6 pt-6 pb-4">
<h2 className="text-lg font-medium text-gray-900">Directory</h2>
<p className="mt-1 text-sm text-gray-600">Search directory of 3,018 employees</p>
<form className="mt-6 flex space-x-4" action="#">
<div className="flex-1 min-w-0">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative rounded-md shadow-sm">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<SearchIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
<input
type="search"
name="search"
id="search"
className="focus:ring-pink-500 focus:border-pink-500 block w-full pl-10 sm:text-sm border-gray-300 rounded-md"
placeholder="Search"
/>
</div>
</div>
<button
type="submit"
className="inline-flex justify-center px-3.5 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500"
>
<FilterIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
<span className="sr-only">Search</span>
</button>
</form>
</div>
{/* Directory list */}
<nav className="flex-1 min-h-0 overflow-y-auto" aria-label="Directory">
{Object.keys(directory).map((letter) => (
<div key={letter} className="relative">
<div className="z-10 sticky top-0 border-t border-b border-gray-200 bg-gray-50 px-6 py-1 text-sm font-medium text-gray-500">
<h3>{letter}</h3>
</div>
<ul role="list" className="relative z-0 divide-y divide-gray-200">
{directory[letter].map((person) => (
<li key={person.id}>
<div className="relative px-6 py-5 flex items-center space-x-3 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-pink-500">
<div className="flex-shrink-0">
<img className="h-10 w-10 rounded-full" src={person.imageUrl} alt="" />
</div>
<div className="flex-1 min-w-0">
<a href="#" className="focus:outline-none">
{/* Extend touch target to entire panel */}
<span className="absolute inset-0" aria-hidden="true" />
<p className="text-sm font-medium text-gray-900">{person.name}</p>
<p className="text-sm text-gray-500 truncate">{person.role}</p>
</a>
</div>
</div>
</li>
))}
</ul>
</div>
))}
</nav>
</aside>
</div>
</div>
</div>
</>
)
}
/* This example requires Tailwind CSS v2.0+ */
export default function Example() {
return (
<button
type="button"
className="relative block w-full border-2 border-gray-300 border-dashed rounded-lg p-12 text-center hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<svg
className="mx-auto h-12 w-12 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M8 14v20c0 4.418 7.163 8 16 8 1.381 0 2.721-.087 4-.252M8 14c0 4.418 7.163 8 16 8s16-3.582 16-8M8 14c0-4.418 7.163-8 16-8s16 3.582 16 8m0 0v14m0-4c0 4.418-7.163 8-16 8S8 28.418 8 24m32 10v6m0 0v6m0-6h6m-6 0h-6"
/>
</svg>
<span className="mt-2 block text-sm font-medium text-gray-900">Create a new database</span>
</button>
)
}
app/route/dashboard/users.tsx
file as a route shell rendering all the users subscribing to the Author as well as Remix's Outlet component in the content areaapp/route/dashboard/users/index.tsx
rendering the empty state With dashed borderapp/route/dashboard/users.tsx
file to load all the users subscribing to the Author's courses then show the data into the screenBuild a component shell for the /dashboard/purchase
route to facilitate the Member-facing user interface in this Transaction Flow diagram.
The purchase page should render this Circles with text steps component.
/dashboard/purchase
route,/* This example requires Tailwind CSS v2.0+ */
import { CheckIcon } from '@heroicons/react/solid'
const steps = [
{ name: 'Create account', description: 'Vitae sed mi luctus laoreet.', href: '#', status: 'complete' },
{
name: 'Profile information',
description: 'Cursus semper viverra facilisis et et some more.',
href: '#',
status: 'current',
},
{ name: 'Business information', description: 'Penatibus eu quis ante.', href: '#', status: 'upcoming' },
{ name: 'Theme', description: 'Faucibus nec enim leo et.', href: '#', status: 'upcoming' },
{ name: 'Preview', description: 'Iusto et officia maiores porro ad non quas.', href: '#', status: 'upcoming' },
]
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Example() {
return (
<nav aria-label="Progress">
<ol role="list" className="overflow-hidden">
{steps.map((step, stepIdx) => (
<li key={step.name} className={classNames(stepIdx !== steps.length - 1 ? 'pb-10' : '', 'relative')}>
{step.status === 'complete' ? (
<>
{stepIdx !== steps.length - 1 ? (
<div className="-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full bg-indigo-600" aria-hidden="true" />
) : null}
<a href={step.href} className="relative flex items-start group">
<span className="h-9 flex items-center">
<span className="relative z-10 w-8 h-8 flex items-center justify-center bg-indigo-600 rounded-full group-hover:bg-indigo-800">
<CheckIcon className="w-5 h-5 text-white" aria-hidden="true" />
</span>
</span>
<span className="ml-4 min-w-0 flex flex-col">
<span className="text-xs font-semibold tracking-wide uppercase">{step.name}</span>
<span className="text-sm text-gray-500">{step.description}</span>
</span>
</a>
</>
) : step.status === 'current' ? (
<>
{stepIdx !== steps.length - 1 ? (
<div className="-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full bg-gray-300" aria-hidden="true" />
) : null}
<a href={step.href} className="relative flex items-start group" aria-current="step">
<span className="h-9 flex items-center" aria-hidden="true">
<span className="relative z-10 w-8 h-8 flex items-center justify-center bg-white border-2 border-indigo-600 rounded-full">
<span className="h-2.5 w-2.5 bg-indigo-600 rounded-full" />
</span>
</span>
<span className="ml-4 min-w-0 flex flex-col">
<span className="text-xs font-semibold tracking-wide uppercase text-indigo-600">{step.name}</span>
<span className="text-sm text-gray-500">{step.description}</span>
</span>
</a>
</>
) : (
<>
{stepIdx !== steps.length - 1 ? (
<div className="-ml-px absolute mt-0.5 top-4 left-4 w-0.5 h-full bg-gray-300" aria-hidden="true" />
) : null}
<a href={step.href} className="relative flex items-start group">
<span className="h-9 flex items-center" aria-hidden="true">
<span className="relative z-10 w-8 h-8 flex items-center justify-center bg-white border-2 border-gray-300 rounded-full group-hover:border-gray-400">
<span className="h-2.5 w-2.5 bg-transparent rounded-full group-hover:bg-gray-300" />
</span>
</span>
<span className="ml-4 min-w-0 flex flex-col">
<span className="text-xs font-semibold tracking-wide uppercase text-gray-500">{step.name}</span>
<span className="text-sm text-gray-500">{step.description}</span>
</span>
</a>
</>
)}
</li>
))}
</ol>
</nav>
)
}
/dashboard/purchase/index.tsx
route fileTransaction entity to store payment information.
Please refer to the Entity Relationship Diagram.
Transaction
table in schema.prisma
Course
, Subscription
, & Transaction
data in seed.ts
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.