Add settings section

pull/2/head
Bailey Pumfleet 2021-04-07 16:03:02 +01:00
parent 443febac8d
commit f55f2f6321
12 changed files with 509 additions and 7 deletions

90
components/Settings.tsx Normal file
View File

@ -0,0 +1,90 @@
import Link from 'next/link';
import { useRouter } from "next/router";
export default function SettingsShell(props) {
const router = useRouter();
return (
<div>
<main className="relative -mt-32">
<div>
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
<aside className="py-6 lg:col-span-3">
<nav className="space-y-1">
<Link href="/settings/profile">
<a className={router.pathname == "/settings/profile" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"} aria-current="page">
<svg className={router.pathname == "/settings/profile" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="truncate">
Profile
</span>
</a>
</Link>
{/* <Link href="/settings/account">
<a className={router.pathname == "/settings/account" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
<svg className={router.pathname == "/settings/account" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span className="truncate">
Account
</span>
</a>
</Link> */}
<Link href="/settings/password">
<a className={router.pathname == "/settings/password" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
<svg className={router.pathname == "/settings/password" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
</svg>
<span className="truncate">
Password
</span>
</a>
</Link>
{/* <Link href="/settings/notifications">
<a className={router.pathname == "/settings/notifications" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
<svg className={router.pathname == "/settings/notifications" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
<span className="truncate">
Notifications
</span>
</a>
</Link>
<Link href="/settings/billing">
<a className={router.pathname == "/settings/billing" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
<svg className={router.pathname == "/settings/billing" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
</svg>
<span className="truncate">
Billing
</span>
</a>
</Link>
<Link href="/settings/integrations">
<a className={router.pathname == "/settings/integrations" ? "bg-blue-50 border-blue-500 text-blue-700 hover:bg-blue-50 hover:text-blue-700 group border-l-4 px-3 py-2 flex items-center text-sm font-medium" : "border-transparent text-gray-900 hover:bg-gray-50 hover:text-gray-900 group border-l-4 px-3 py-2 flex items-center text-sm font-medium"}>
<svg className={router.pathname == "/settings/integrations" ? "text-blue-500 group-hover:text-blue-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6" : "text-gray-400 group-hover:text-gray-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 14v6m-3-3h6M6 10h2a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2zm10 0h2a2 2 0 002-2V6a2 2 0 00-2-2h-2a2 2 0 00-2 2v2a2 2 0 002 2zM6 20h2a2 2 0 002-2v-2a2 2 0 00-2-2H6a2 2 0 00-2 2v2a2 2 0 002 2z" />
</svg>
<span className="truncate">
Integrations
</span>
</a>
</Link> */}
</nav>
</aside>
{props.children}
</div>
</div>
</div>
</main>
</div>
);
}

View File

@ -21,7 +21,7 @@ export default function Shell(props) {
<div className="flex items-center justify-between h-16 px-4 sm:px-0">
<div className="flex items-center">
<div className="flex-shrink-0">
<img className="h-6" src="calendso-white.svg" alt="Calendso" />
<img className="h-6" src="/calendso-white.svg" alt="Calendso" />
</div>
<div className="hidden md:block">
<div className="ml-10 flex items-baseline space-x-4">
@ -37,8 +37,8 @@ export default function Shell(props) {
<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>
</Link>
<Link href="/">
<a className={router.pathname.startsWith("/team") ? "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"}>Team</a>
<Link href="/settings">
<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>
</Link>
</div>
</div>

View File

@ -0,0 +1,47 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { hashPassword, verifyPassword } from '../../../lib/auth';
import { getSession } from 'next-auth/client';
import prisma from '../../../lib/prisma';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({req: req});
if (!session) {
res.status(401).json({message: "Not authenticated"});
return;
}
// TODO: Add user ID to user session object
const user = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true,
password: true
}
});
if (!user) { res.status(404).json({message: 'User not found'}); return; }
const oldPassword = req.body.oldPassword;
const newPassword = req.body.newPassword;
const currentPassword = user.password;
const passwordsMatch = await verifyPassword(oldPassword, currentPassword);
if (!passwordsMatch) { res.status(403).json({message: 'Incorrect password'}); return; }
const hashedPassword = await hashPassword(newPassword);
const updateUser = await prisma.user.update({
where: {
id: user.id,
},
data: {
password: hashedPassword,
},
});
res.status(200).json({message: 'Password updated successfully'});
}

View File

@ -1,5 +1,5 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import prisma from '../../../lib/prisma'
import type { NextApiRequest, NextApiResponse } from 'next';
import prisma from '../../../lib/prisma';
const {google} = require('googleapis');
const credentials = process.env.GOOGLE_API_CREDENTIALS;

42
pages/api/user/profile.ts Normal file
View File

@ -0,0 +1,42 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSession } from 'next-auth/client';
import prisma from '../../../lib/prisma';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({req: req});
if (!session) {
res.status(401).json({message: "Not authenticated"});
return;
}
// TODO: Add user ID to user session object
const user = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true,
password: true
}
});
if (!user) { res.status(404).json({message: 'User not found'}); return; }
const username = req.body.username;
const name = req.body.name;
const description = req.body.description;
const updateUser = await prisma.user.update({
where: {
id: user.id,
},
data: {
username: username,
name: name,
bio: description
},
});
res.status(200).json({message: 'Profile updated successfully'});
}

View File

@ -18,7 +18,7 @@ export default function Error() {
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
<svg className="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div className="mt-3 text-center sm:mt-5">

43
pages/auth/logout.tsx Normal file
View File

@ -0,0 +1,43 @@
import Head from 'next/head';
import Link from 'next/link';
export default function Logout() {
return (
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<Head>
<title>Logged out - Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<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-red-100">
<svg className="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
You've been logged out
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">
We hope to see you again soon!
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-6">
<Link href="/auth/login">
<a className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:text-sm">
Go back to the login page
</a>
</Link>
</div>
</div>
</div>
</div>
);
}

19
pages/settings/index.tsx Normal file
View File

@ -0,0 +1,19 @@
import { useRouter } from 'next/router';
import { signIn, useSession, getSession } from 'next-auth/client';
export default function Settings() {
const [ session, loading ] = useSession();
const router = useRouter();
if (loading) {
return <p className="text-gray-400">Loading...</p>;
} else {
if (!session) {
window.location.href = "/auth/login";
}
}
router.push('/settings/profile');
return null;
}

104
pages/settings/password.tsx Normal file
View File

@ -0,0 +1,104 @@
import Head from 'next/head';
import Link from 'next/link';
import { useRef } from 'react';
import prisma from '../../lib/prisma';
import Shell from '../../components/Shell';
import SettingsShell from '../../components/Settings';
import { signIn, useSession, getSession } from 'next-auth/client';
export default function Settings(props) {
const [ session, loading ] = useSession();
const oldPasswordRef = useRef();
const newPasswordRef = useRef();
if (loading) {
return <p className="text-gray-400">Loading...</p>;
} else {
if (!session) {
window.location.href = "/auth/login";
}
}
async function changePasswordHandler(event) {
event.preventDefault();
const enteredOldPassword = oldPasswordRef.current.value;
const enteredNewPassword = newPasswordRef.current.value;
// TODO: Add validation
const response = await fetch('/api/auth/changepw', {
method: 'PATCH',
body: JSON.stringify({oldPassword: enteredOldPassword, newPassword: enteredNewPassword}),
headers: {
'Content-Type': 'application/json'
}
});
console.log(response);
}
return(
<Shell title="Password">
<Head>
<title>Change Password | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<SettingsShell>
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={changePasswordHandler}>
<div className="py-6 px-4 sm:p-6 lg:pb-8">
<div>
<h2 className="text-lg leading-6 font-medium text-gray-900">Change Password</h2>
<p className="mt-1 text-sm text-gray-500">
Change the password for your Calendso account.
</p>
</div>
<div className="mt-6 flex">
<div className="w-1/2 mr-2">
<label htmlFor="current_password" className="block text-sm font-medium text-gray-700">Current Password</label>
<div className="mt-1">
<input ref={oldPasswordRef} type="password" name="current_password" id="current_password" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="Your old password" />
</div>
</div>
<div className="w-1/2 ml-2">
<label htmlFor="new_password" className="block text-sm font-medium text-gray-700">New Password</label>
<div className="mt-1">
<input ref={newPasswordRef} type="password" name="new_password" id="new_password" className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="Your super secure new password" />
</div>
</div>
</div>
<hr className="mt-8" />
<div className="py-4 flex justify-end">
<button type="button" className="bg-white border border-gray-300 rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Cancel
</button>
<button type="submit" className="ml-2 bg-blue-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Save
</button>
</div>
</div>
</form>
</SettingsShell>
</Shell>
);
}
export async function getServerSideProps(context) {
const session = await getSession(context);
const user = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true,
username: true,
name: true
}
});
return {
props: {user}, // will be passed to the page component as props
}
}

157
pages/settings/profile.tsx Normal file
View File

@ -0,0 +1,157 @@
import Head from 'next/head';
import Link from 'next/link';
import { useRef } from 'react';
import prisma from '../../lib/prisma';
import Shell from '../../components/Shell';
import SettingsShell from '../../components/Settings';
import { signIn, useSession, getSession } from 'next-auth/client';
export default function Settings(props) {
const [ session, loading ] = useSession();
const usernameRef = useRef();
const nameRef = useRef();
const descriptionRef = useRef();
if (loading) {
return <p className="text-gray-400">Loading...</p>;
} else {
if (!session) {
window.location.href = "/auth/login";
}
}
async function updateProfileHandler(event) {
event.preventDefault();
const enteredUsername = usernameRef.current.value;
const enteredName = nameRef.current.value;
const enteredDescription = descriptionRef.current.value;
// TODO: Add validation
const response = await fetch('/api/user/profile', {
method: 'PATCH',
body: JSON.stringify({username: enteredUsername, name: enteredName, description: enteredDescription}),
headers: {
'Content-Type': 'application/json'
}
});
console.log(response);
}
return(
<Shell title="Profile">
<Head>
<title>Profile | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<SettingsShell>
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}>
<div className="py-6 px-4 sm:p-6 lg:pb-8">
<div>
<h2 className="text-lg leading-6 font-medium text-gray-900">Profile</h2>
<p className="mt-1 text-sm text-gray-500">
Review and change your public page details.
</p>
</div>
<div className="mt-6 flex flex-col lg:flex-row">
<div className="flex-grow space-y-6">
<div className="flex">
<div className="w-1/2 mr-2">
<label htmlFor="username" className="block text-sm font-medium text-gray-700">
Username
</label>
<div className="mt-1 rounded-md shadow-sm flex">
<span className="bg-gray-50 border border-r-0 border-gray-300 rounded-l-md px-3 inline-flex items-center text-gray-500 sm:text-sm">
{window.location.hostname}/
</span>
<input ref={usernameRef} type="text" name="username" id="username" autoComplete="username" className="focus:ring-blue-500 focus:border-blue-500 flex-grow block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300" defaultValue={props.user.username} />
</div>
</div>
<div className="w-1/2 ml-2">
<label htmlFor="name" className="block text-sm font-medium text-gray-700">Full name</label>
<input ref={nameRef} type="text" name="name" id="name" autoComplete="given-name" placeholder="Your name" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" value={props.user.name} />
</div>
</div>
<div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700">
About
</label>
<div className="mt-1">
<textarea ref={descriptionRef} id="about" name="about" placeholder="A little something about yourself." rows={3} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md">{props.user.bio}</textarea>
</div>
</div>
</div>
<div className="mt-6 flex-grow lg:mt-0 lg:ml-6 lg:flex-grow-0 lg:flex-shrink-0">
<p className="mb-2 text-sm font-medium text-gray-700" aria-hidden="true">
Photo
</p>
<div className="mt-1 lg:hidden">
<div className="flex items-center">
<div className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12" aria-hidden="true">
<img className="rounded-full h-full w-full" src={props.user.avatar} alt="" />
</div>
<div className="ml-5 rounded-md shadow-sm">
<div className="group relative border border-gray-300 rounded-md py-2 px-3 flex items-center justify-center hover:bg-gray-50 focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-blue-500">
<label htmlFor="user_photo" className="relative text-sm leading-4 font-medium text-gray-700 pointer-events-none">
<span>Change</span>
<span className="sr-only"> user photo</span>
</label>
<input id="user_photo" name="user_photo" type="file" className="absolute w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-md" />
</div>
</div>
</div>
</div>
<div className="hidden relative rounded-full overflow-hidden lg:block">
{props.user.avatar && <img className="relative rounded-full w-40 h-40" src={props.user.avatar} alt="" />}
{!props.user.avatar && <div className="relative bg-blue-600 rounded-full w-40 h-40"></div>}
<label htmlFor="user-photo" className="absolute inset-0 w-full h-full bg-black bg-opacity-75 flex items-center justify-center text-sm font-medium text-white opacity-0 hover:opacity-100 focus-within:opacity-100">
<span>Change</span>
<span className="sr-only"> user photo</span>
<input type="file" id="user-photo" name="user-photo" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer border-gray-300 rounded-md" />
</label>
</div>
</div>
</div>
<hr className="mt-8" />
<div className="py-4 flex justify-end">
<button type="button" className="bg-white border border-gray-300 rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Cancel
</button>
<button type="submit" className="ml-2 bg-blue-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Save
</button>
</div>
</div>
</form>
</SettingsShell>
</Shell>
);
}
export async function getServerSideProps(context) {
const session = await getSession(context);
const user = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true,
username: true,
name: true,
email: true,
bio: true,
avatar: true
}
});
return {
props: {user}, // will be passed to the page component as props
}
}

View File

@ -24,7 +24,7 @@ export default function Success(props) {
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
<svg className="h-6 w-6 text-green-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7" />
</svg>
</div>
<div className="mt-3 text-center sm:mt-5">