Update dashboard
This commit is contained in:
parent
209791d86d
commit
3a6eae8b8f
4 changed files with 235 additions and 27 deletions
|
@ -36,10 +36,10 @@ export default function Shell(props) {
|
|||
|
||||
return session && (
|
||||
<div>
|
||||
<div className="bg-gray-800 pb-32">
|
||||
<nav className="bg-gray-800">
|
||||
<div className="bg-gradient-to-b from-blue-600 via-blue-600 to-blue-300 pb-32">
|
||||
<nav className="bg-blue-600">
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div className="border-b border-gray-700">
|
||||
<div className="border-b border-blue-500">
|
||||
<div className="flex items-center justify-between h-16 px-4 sm:px-0">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
|
@ -48,19 +48,19 @@ export default function Shell(props) {
|
|||
<div className="hidden md:block">
|
||||
<div className="ml-10 flex items-baseline space-x-4">
|
||||
<Link href="/">
|
||||
<a className={router.pathname == "/" ? "bg-gray-700 text-white px-3 py-2 rounded-md text-sm font-medium" : "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Dashboard</a>
|
||||
<a className={router.pathname == "/" ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Dashboard</a>
|
||||
</Link>
|
||||
{/* <Link href="/">
|
||||
<a className={router.pathname.startsWith("/bookings") ? "bg-gray-700 text-white px-3 py-2 rounded-md text-sm font-medium" : "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Bookings</a>
|
||||
<a className={router.pathname.startsWith("/bookings") ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Bookings</a>
|
||||
</Link> */}
|
||||
<Link href="/availability">
|
||||
<a className={router.pathname.startsWith("/availability") ? "bg-gray-700 text-white px-3 py-2 rounded-md text-sm font-medium" : "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Availability</a>
|
||||
<a className={router.pathname.startsWith("/availability") ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Availability</a>
|
||||
</Link>
|
||||
<Link href="/integrations">
|
||||
<a className={router.pathname.startsWith("/integrations") ? "bg-gray-700 text-white px-3 py-2 rounded-md text-sm font-medium" : "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Integrations</a>
|
||||
<a className={router.pathname.startsWith("/integrations") ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Integrations</a>
|
||||
</Link>
|
||||
<Link href="/settings/profile">
|
||||
<a className={router.pathname.startsWith("/settings") ? "bg-gray-700 text-white px-3 py-2 rounded-md text-sm font-medium" : "text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Settings</a>
|
||||
<a className={router.pathname.startsWith("/settings") ? "bg-blue-500 transition-colors duration-300 ease-in-out text-white px-3 py-2 rounded-md text-sm font-medium" : "text-white hover:bg-blue-500 transition-colors duration-300 ease-in-out hover:text-white px-3 py-2 rounded-md text-sm font-medium"}>Settings</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -71,7 +71,7 @@ export default function Shell(props) {
|
|||
<div>
|
||||
<button onClick={toggleProfileDropdown} type="button" className="max-w-xs bg-gray-800 rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" id="user-menu" aria-expanded="false" aria-haspopup="true">
|
||||
<span className="sr-only">Open user menu</span>
|
||||
<img className="h-8 w-8 rounded-full" src={"https://eu.ui-avatars.com/api/?background=039be5&color=fff&name=" + encodeURIComponent(session.user.name || "")} alt="" />
|
||||
<img className="h-8 w-8 rounded-full" src={session.user.image ? session.user.image : "https://eu.ui-avatars.com/api/?background=fff&color=039be5&name=" + encodeURIComponent(session.user.name || "")} alt="" />
|
||||
</button>
|
||||
</div>
|
||||
{
|
||||
|
|
|
@ -36,7 +36,7 @@ export default NextAuth({
|
|||
throw new Error('Incorrect password');
|
||||
}
|
||||
|
||||
return {id: user.id, username: user.username, email: user.email, name: user.name};
|
||||
return {id: user.id, username: user.username, email: user.email, name: user.name, image: user.avatar};
|
||||
}
|
||||
})
|
||||
],
|
||||
|
|
|
@ -110,7 +110,7 @@ export default function Availability(props) {
|
|||
Event Types
|
||||
</h3>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<button onClick={toggleAddModal} type="button" className="btn-sm btn-primary">
|
||||
<button onClick={toggleAddModal} type="button" className="btn-sm btn-white">
|
||||
New event type
|
||||
</button>
|
||||
</div>
|
||||
|
|
234
pages/index.tsx
234
pages/index.tsx
|
@ -3,14 +3,66 @@ import Link from 'next/link';
|
|||
import prisma from '../lib/prisma';
|
||||
import Shell from '../components/Shell';
|
||||
import { signIn, useSession, getSession } from 'next-auth/client';
|
||||
import { ClockIcon, CheckIcon, InformationCircleIcon } from '@heroicons/react/outline';
|
||||
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
export default function Home(props) {
|
||||
const [ session, loading ] = useSession();
|
||||
const [session, loading] = useSession();
|
||||
if (loading) {
|
||||
return <p className="text-gray-400">Loading...</p>;
|
||||
}
|
||||
|
||||
return(
|
||||
function convertMinsToHrsMins(mins) {
|
||||
let h = Math.floor(mins / 60);
|
||||
let m = mins % 60;
|
||||
h = h < 10 ? '0' + h : h;
|
||||
m = m < 10 ? '0' + m : m;
|
||||
return `${h}:${m}`;
|
||||
}
|
||||
|
||||
const stats = [
|
||||
{ name: 'Event Types', stat: props.eventTypeCount },
|
||||
{ name: 'Integrations', stat: props.integrationCount },
|
||||
{ name: 'Available Hours', stat: (props.user.endTime - props.user.startTime) / 60 + ' hours' },
|
||||
];
|
||||
|
||||
let timeline = [];
|
||||
|
||||
if (session) {
|
||||
timeline = [
|
||||
{
|
||||
id: 1,
|
||||
content: 'Add your first',
|
||||
target: 'integration',
|
||||
href: '/integrations',
|
||||
icon: props.integrationCount != 0 ? CheckIcon : InformationCircleIcon,
|
||||
iconBackground: props.integrationCount != 0 ? 'bg-green-400' : 'bg-gray-400',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content: 'Add one or more',
|
||||
target: 'event types',
|
||||
href: '/availability',
|
||||
icon: props.eventTypeCount != 0 ? CheckIcon : InformationCircleIcon,
|
||||
iconBackground: props.eventTypeCount != 0 ? 'bg-green-400' : 'bg-gray-400',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
content: 'Complete your',
|
||||
target: 'profile',
|
||||
href: '/settings/profile',
|
||||
icon: session.user.image ? CheckIcon : InformationCircleIcon,
|
||||
iconBackground: session.user.image ? 'bg-green-400' : 'bg-gray-400',
|
||||
},
|
||||
];
|
||||
} else {
|
||||
timeline = [];
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>Calendso</title>
|
||||
|
@ -20,26 +72,124 @@ export default function Home(props) {
|
|||
<Shell heading="Dashboard">
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="col-span-2">
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="px-4 py-5 sm:p-6">
|
||||
<div className="rounded-lg bg-white shadow">
|
||||
<div className="pt-5 pb-2 px-6 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Welcome to Calendso!
|
||||
Your stats
|
||||
</h3>
|
||||
<div className="mt-2 max-w-xl text-sm text-gray-500">
|
||||
</div>
|
||||
<dl className="grid grid-cols-1 overflow-hidden divide-y divide-gray-200 md:grid-cols-3 md:divide-y-0 md:divide-x">
|
||||
{stats.map((item) => (
|
||||
<div key={item.name} className="px-4 py-5 sm:p-6">
|
||||
<dt className="text-base font-normal text-gray-900">{item.name}</dt>
|
||||
<dd className="mt-1 flex justify-between items-baseline md:block lg:flex">
|
||||
<div className="flex items-baseline text-2xl font-semibold text-blue-600">
|
||||
{item.stat}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
<div className="mt-8 bg-white shadow overflow-hidden sm:rounded-md">
|
||||
<div className="pt-5 pb-2 px-6 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Your event types
|
||||
</h3>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-200">
|
||||
{props.eventTypes.map((type) => (
|
||||
<li key={type.id}>
|
||||
<div className="px-4 py-4 flex items-center sm:px-6">
|
||||
<div className="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div className="truncate">
|
||||
<div className="flex text-sm">
|
||||
<p className="font-medium text-blue-600 truncate">{type.title}</p>
|
||||
<p className="ml-1 flex-shrink-0 font-normal text-gray-500">in {type.description}</p>
|
||||
</div>
|
||||
<div className="mt-2 flex">
|
||||
<div className="flex items-center text-sm text-gray-500">
|
||||
<ClockIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||
<p>
|
||||
Get started by connecting your first calendar integration, enabling us to fetch your availability. Head over to the integrations page, and click the add link.
|
||||
{type.length} minutes
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-3 text-sm">
|
||||
<Link href="/integrations">
|
||||
<a className="font-medium text-blue-600 hover:text-blue-500"> Set up your first integration <span aria-hidden="true">→</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-5 flex-shrink-0">
|
||||
<Link href={"/" + session.user.username + "/" + type.slug}><a target="_blank" className="text-blue-600 hover:text-blue-900 mr-2 font-medium">View</a></Link>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-8 bg-white shadow overflow-hidden sm:rounded-md p-6">
|
||||
<div className="flex">
|
||||
<div className="w-1/2 self-center">
|
||||
<h2 className="text-2xl font-semibold">Getting started</h2>
|
||||
<p className="text-gray-600 text-sm">Steps you should take to get started with Calendso.</p>
|
||||
</div>
|
||||
<div className="w-1/2">
|
||||
<div className="flow-root">
|
||||
<ul className="-mb-8">
|
||||
{timeline.map((event, eventIdx) => (
|
||||
<li key={event.id}>
|
||||
<div className="relative pb-8">
|
||||
{eventIdx !== 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(
|
||||
event.iconBackground,
|
||||
'h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white'
|
||||
)}
|
||||
>
|
||||
<event.icon className="h-5 w-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">
|
||||
{event.content}{' '}
|
||||
<Link href={event.href}>
|
||||
<a className="font-medium text-gray-900">
|
||||
{event.target}
|
||||
</a>
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-white rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
|
||||
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Your day
|
||||
</h3>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<Link href="/availability">
|
||||
<a className="text-sm text-gray-400">Configure</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-2xl font-semibold text-gray-600">Offering time slots between <span className="text-blue-600">{convertMinsToHrsMins(props.user.startTime)}</span> and <span className="text-blue-600">{convertMinsToHrsMins(props.user.endTime)}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 bg-white rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
|
||||
<div className="mb-8 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Your integrations
|
||||
|
@ -69,6 +219,39 @@ export default function Home(props) {
|
|||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="mt-8 bg-white rounded-lg shadow px-5 py-6 md:py-7 sm:px-6">
|
||||
<div className="mb-4 sm:flex sm:items-center sm:justify-between">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Your event types
|
||||
</h3>
|
||||
<div className="mt-3 sm:mt-0 sm:ml-4">
|
||||
<Link href="/availability">
|
||||
<a className="text-sm text-gray-400">View more</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-200">
|
||||
{props.eventTypes.map((type) => (
|
||||
<li
|
||||
key={type.id}
|
||||
className="relative bg-white py-5 hover:bg-gray-50 focus-within:ring-2 focus-within:ring-inset focus-within:ring-indigo-600"
|
||||
>
|
||||
<div className="flex justify-between space-x-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<a href="#" className="block focus:outline-none">
|
||||
<span className="absolute inset-0" aria-hidden="true" />
|
||||
<p className="text-sm font-medium text-gray-900 truncate">{type.title}</p>
|
||||
<p className="text-sm text-gray-500 truncate">{type.description}</p>
|
||||
</a>
|
||||
</div>
|
||||
<span className="flex-shrink-0 whitespace-nowrap text-sm text-gray-500">
|
||||
{type.length} minutes
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Shell>
|
||||
|
@ -79,15 +262,22 @@ export default function Home(props) {
|
|||
export async function getServerSideProps(context) {
|
||||
const session = await getSession(context);
|
||||
|
||||
let user = [];
|
||||
let credentials = [];
|
||||
let eventTypes = [];
|
||||
|
||||
let eventTypeCount = 0;
|
||||
let integrationCount = 0;
|
||||
|
||||
if (session) {
|
||||
const user = await prisma.user.findFirst({
|
||||
user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
id: true,
|
||||
startTime: true,
|
||||
endTime: true
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -99,8 +289,26 @@ export async function getServerSideProps(context) {
|
|||
type: true
|
||||
}
|
||||
});
|
||||
|
||||
eventTypes = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
}
|
||||
});
|
||||
|
||||
eventTypeCount = await prisma.eventType.count({
|
||||
where: {
|
||||
userId: session.user.id
|
||||
}
|
||||
});
|
||||
|
||||
integrationCount = await prisma.credential.count({
|
||||
where: {
|
||||
userId: session.user.id
|
||||
}
|
||||
});
|
||||
}
|
||||
return {
|
||||
props: {credentials}, // will be passed to the page component as props
|
||||
props: { user, credentials, eventTypes, eventTypeCount, integrationCount }, // will be passed to the page component as props
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue