Update dashboard

This commit is contained in:
Bailey Pumfleet 2021-05-11 14:11:17 +01:00
parent 209791d86d
commit 3a6eae8b8f
4 changed files with 235 additions and 27 deletions

View file

@ -36,10 +36,10 @@ export default function Shell(props) {
return session && ( return session && (
<div> <div>
<div className="bg-gray-800 pb-32"> <div className="bg-gradient-to-b from-blue-600 via-blue-600 to-blue-300 pb-32">
<nav className="bg-gray-800"> <nav className="bg-blue-600">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8"> <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 justify-between h-16 px-4 sm:px-0">
<div className="flex items-center"> <div className="flex items-center">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
@ -48,19 +48,19 @@ export default function Shell(props) {
<div className="hidden md:block"> <div className="hidden md:block">
<div className="ml-10 flex items-baseline space-x-4"> <div className="ml-10 flex items-baseline space-x-4">
<Link href="/"> <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>
{/* <Link href="/"> {/* <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> */}
<Link href="/availability"> <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>
<Link href="/integrations"> <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>
<Link href="/settings/profile"> <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> </Link>
</div> </div>
</div> </div>
@ -71,7 +71,7 @@ export default function Shell(props) {
<div> <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"> <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> <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> </button>
</div> </div>
{ {

View file

@ -36,7 +36,7 @@ export default NextAuth({
throw new Error('Incorrect password'); 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};
} }
}) })
], ],

View file

@ -110,7 +110,7 @@ export default function Availability(props) {
Event Types Event Types
</h3> </h3>
<div className="mt-3 sm:mt-0 sm:ml-4"> <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 New event type
</button> </button>
</div> </div>

View file

@ -3,14 +3,66 @@ import Link from 'next/link';
import prisma from '../lib/prisma'; import prisma from '../lib/prisma';
import Shell from '../components/Shell'; import Shell from '../components/Shell';
import { signIn, useSession, getSession } from 'next-auth/client'; 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) { export default function Home(props) {
const [ session, loading ] = useSession(); const [session, loading] = useSession();
if (loading) { if (loading) {
return <p className="text-gray-400">Loading...</p>; 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> <div>
<Head> <Head>
<title>Calendso</title> <title>Calendso</title>
@ -20,26 +72,124 @@ export default function Home(props) {
<Shell heading="Dashboard"> <Shell heading="Dashboard">
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<div className="col-span-2"> <div className="col-span-2">
<div className="bg-white shadow rounded-lg"> <div className="rounded-lg bg-white shadow">
<div className="px-4 py-5 sm:p-6"> <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"> <h3 className="text-lg leading-6 font-medium text-gray-900">
Welcome to Calendso! Your stats
</h3> </h3>
<div className="mt-2 max-w-xl text-sm text-gray-500"> </div>
<p> <dl className="grid grid-cols-1 overflow-hidden divide-y divide-gray-200 md:grid-cols-3 md:divide-y-0 md:divide-x">
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. {stats.map((item) => (
</p> <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>
{type.length} minutes
</p>
</div>
</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>
<div className="mt-3 text-sm"> <div className="w-1/2">
<Link href="/integrations"> <div className="flow-root">
<a className="font-medium text-blue-600 hover:text-blue-500"> Set up your first integration <span aria-hidden="true">&rarr;</span></a> <ul className="-mb-8">
</Link> {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>
</div> </div>
<div> <div>
<div className="bg-white rounded-lg shadow px-5 py-6 md:py-7 sm:px-6"> <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"> <div className="mb-8 sm:flex sm:items-center sm:justify-between">
<h3 className="text-lg leading-6 font-medium text-gray-900"> <h3 className="text-lg leading-6 font-medium text-gray-900">
Your integrations Your integrations
@ -69,6 +219,39 @@ export default function Home(props) {
} }
</ul> </ul>
</div> </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>
</div> </div>
</Shell> </Shell>
@ -79,15 +262,22 @@ export default function Home(props) {
export async function getServerSideProps(context) { export async function getServerSideProps(context) {
const session = await getSession(context); const session = await getSession(context);
let user = [];
let credentials = []; let credentials = [];
let eventTypes = [];
let eventTypeCount = 0;
let integrationCount = 0;
if (session) { if (session) {
const user = await prisma.user.findFirst({ user = await prisma.user.findFirst({
where: { where: {
email: session.user.email, email: session.user.email,
}, },
select: { select: {
id: true id: true,
startTime: true,
endTime: true
} }
}); });
@ -99,8 +289,26 @@ export async function getServerSideProps(context) {
type: true 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 { 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
} }
} }