Link to iron-session example (#62808)
## What? The current example is outdated so I've removed it and linked to the package's own example. <!-- Thanks for opening a PR! Your contribution is much appreciated. To make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below. Choose the right checklist for the change(s) that you're making: ## For Contributors ### Improving Documentation - Run `pnpm prettier-fix` to fix formatting issues before opening the PR. - Read the Docs Contribution Guide to ensure your contribution follows the docs guidelines: https://nextjs.org/docs/community/contribution-guide ### Adding or Updating Examples - The "examples guidelines" are followed from our contributing doc https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md - Make sure the linting passes by running `pnpm build && pnpm lint`. See https://github.com/vercel/next.js/blob/canary/contributing/repository/linting.md ### Fixing a bug - Related issues linked using `fixes #number` - Tests added. See: https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ### Adding a feature - Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. (A discussion must be opened, see https://github.com/vercel/next.js/discussions/new?category=ideas) - Related issues/discussions are linked using `fixes #number` - e2e tests added (https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs) - Documentation added - Telemetry added. In case of a feature if it's used or not. - Errors have a helpful link attached, see https://github.com/vercel/next.js/blob/canary/contributing.md ## For Maintainers - Minimal description (aim for explaining to someone not on the team to understand the PR) - When linking to a Slack thread, you might want to share details of the conclusion - Link both the Linear (Fixes NEXT-xxx) and the GitHub issues - Add review comments if necessary to explain to the reviewer the logic behind a change ### What? ### Why? ### How? Closes NEXT- Fixes # --> Closes NEXT-2680
This commit is contained in:
parent
04bda302e4
commit
2f062d61cf
27 changed files with 2 additions and 791 deletions
|
@ -1,6 +0,0 @@
|
|||
# ⚠️ The SECRET_COOKIE_PASSWORD should never be inside your repository directly, it's here only to ease
|
||||
# the example deployment
|
||||
# For local development, you should store it inside a `.env.local` gitignored file
|
||||
# See https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables
|
||||
|
||||
SECRET_COOKIE_PASSWORD=2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8
|
|
@ -1,6 +0,0 @@
|
|||
# ⚠️ The SECRET_COOKIE_PASSWORD should never be inside your repository directly, it's here only to ease
|
||||
# the example deployment
|
||||
# For production you should use https://vercel.com/blog/environment-variables-ui if you're hosted on Vercel or
|
||||
# any other secret environment variable mean
|
||||
|
||||
SECRET_COOKIE_PASSWORD=2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8
|
|
@ -1,46 +1,3 @@
|
|||
# Example application using [`iron-session`](https://github.com/vvo/iron-session)
|
||||
# Next.js + iron-session
|
||||
|
||||
<p align="center"><b>👀 Online demo at <a href="https://iron-session-example.vercel.app/">https://iron-session-example.vercel.app</a></b></p>
|
||||
|
||||
---
|
||||
|
||||
This example creates an authentication system that uses a **signed and encrypted cookie to store session data**. It relies on [`iron-session`](https://github.com/vvo/iron-session).
|
||||
|
||||
It uses current best practices for authentication in the Next.js ecosystem and replicates parts of how the Vercel dashboard is built.
|
||||
|
||||
**Features of the example:**
|
||||
|
||||
- [API Routes](https://nextjs.org/docs/api-routes/dynamic-api-routes) and [getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props) examples.
|
||||
- The logged in status is synchronized between browser windows/tabs using **`useUser`** hook and the [`swr`](https://swr.vercel.app/).
|
||||
- The layout is based on the user's logged-in/out status.
|
||||
- The session data is signed and encrypted in a cookie (this is done automatically by `iron-session`).
|
||||
|
||||
[`iron-session`](https://github.com/vvo/iron-session) also provides:
|
||||
|
||||
- An Express middleware, which can be used in any Node.js HTTP framework.
|
||||
- Multiple encryption keys (passwords) to allow for seamless updates or just password rotation.
|
||||
- Full TypeScript support, including session data.
|
||||
|
||||
## Deploy your own
|
||||
|
||||
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) or preview live with [StackBlitz](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-iron-session)
|
||||
|
||||
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-iron-session&project-name=with-iron-session&repository-name=with-iron-session)
|
||||
|
||||
## How to use
|
||||
|
||||
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example:
|
||||
|
||||
```bash
|
||||
npx create-next-app --example with-iron-session with-iron-session-app
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn create next-app --example with-iron-session with-iron-session-app
|
||||
```
|
||||
|
||||
```bash
|
||||
pnpm create next-app --example with-iron-session with-iron-session-app
|
||||
```
|
||||
|
||||
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||
You can find the [iron-session example here](https://github.com/vvo/iron-session/tree/main/examples/next).
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { FormEvent } from "react";
|
||||
|
||||
export default function Form({
|
||||
errorMessage,
|
||||
onSubmit,
|
||||
}: {
|
||||
errorMessage: string;
|
||||
onSubmit: (e: FormEvent<HTMLFormElement>) => void;
|
||||
}) {
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<label>
|
||||
<span>Type your GitHub username</span>
|
||||
<input type="text" name="username" required />
|
||||
</label>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
|
||||
{errorMessage && <p className="error">{errorMessage}</p>}
|
||||
|
||||
<style jsx>{`
|
||||
form,
|
||||
label {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
label > span {
|
||||
font-weight: 600;
|
||||
}
|
||||
input {
|
||||
padding: 8px;
|
||||
margin: 0.3rem 0 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.error {
|
||||
color: brown;
|
||||
margin: 1rem 0 0;
|
||||
}
|
||||
`}</style>
|
||||
</form>
|
||||
);
|
||||
}
|
|
@ -1,121 +0,0 @@
|
|||
import Link from "next/link";
|
||||
import useUser from "lib/useUser";
|
||||
import { useRouter } from "next/router";
|
||||
import Image from "next/image";
|
||||
import fetchJson from "lib/fetchJson";
|
||||
|
||||
export default function Header() {
|
||||
const { user, mutateUser } = useUser();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/" legacyBehavior>
|
||||
<a>Home</a>
|
||||
</Link>
|
||||
</li>
|
||||
{user?.isLoggedIn === false && (
|
||||
<li>
|
||||
<Link href="/login" legacyBehavior>
|
||||
<a>Login</a>
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
{user?.isLoggedIn === true && (
|
||||
<>
|
||||
<li>
|
||||
<Link href="/profile-sg" legacyBehavior>
|
||||
<a>
|
||||
<span
|
||||
style={{
|
||||
marginRight: ".3em",
|
||||
verticalAlign: "middle",
|
||||
borderRadius: "100%",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src={user.avatarUrl}
|
||||
width={32}
|
||||
height={32}
|
||||
alt=""
|
||||
/>
|
||||
</span>
|
||||
Profile (Static Generation, recommended)
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/profile-ssr" legacyBehavior>
|
||||
<a>Profile (Server-side Rendering)</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/api/logout"
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
mutateUser(
|
||||
await fetchJson("/api/logout", { method: "POST" }),
|
||||
false,
|
||||
);
|
||||
router.push("/login");
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
<li>
|
||||
<a href="https://github.com/vvo/iron-session">
|
||||
<Image
|
||||
src="/GitHub-Mark-Light-32px.png"
|
||||
width="32"
|
||||
height="32"
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<style jsx>{`
|
||||
ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-right: 1rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
li:first-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a img {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 0.2rem;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
}
|
||||
`}</style>
|
||||
</header>
|
||||
);
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import Head from "next/head";
|
||||
import Header from "components/Header";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>With Iron Session</title>
|
||||
</Head>
|
||||
<style jsx global>{`
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, Noto Sans, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 65rem;
|
||||
margin: 1.5rem auto;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
`}</style>
|
||||
<Header />
|
||||
|
||||
<main>
|
||||
<div className="container">{children}</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
export class FetchError extends Error {
|
||||
response: Response;
|
||||
data: {
|
||||
message: string;
|
||||
};
|
||||
constructor({
|
||||
message,
|
||||
response,
|
||||
data,
|
||||
}: {
|
||||
message: string;
|
||||
response: Response;
|
||||
data: {
|
||||
message: string;
|
||||
};
|
||||
}) {
|
||||
// Pass remaining arguments (including vendor specific ones) to parent constructor
|
||||
super(message);
|
||||
|
||||
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, FetchError);
|
||||
}
|
||||
|
||||
this.name = "FetchError";
|
||||
this.response = response;
|
||||
this.data = data ?? { message: message };
|
||||
}
|
||||
}
|
||||
|
||||
export default async function fetchJson<JSON = unknown>(
|
||||
input: RequestInfo,
|
||||
init?: RequestInit,
|
||||
): Promise<JSON> {
|
||||
const response = await fetch(input, init);
|
||||
|
||||
// if the server replies, there's always some data in json
|
||||
// if there's a network error, it will throw at the previous line
|
||||
const data = await response.json();
|
||||
|
||||
// response.ok is true when res.status is 2xx
|
||||
// https://developer.mozilla.org/docs/Web/API/Response/ok
|
||||
if (response.ok) {
|
||||
return data;
|
||||
}
|
||||
|
||||
throw new FetchError({
|
||||
message: response.statusText,
|
||||
response,
|
||||
data,
|
||||
});
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// this file is a wrapper with defaults to be used in both API routes and `getServerSideProps` functions
|
||||
import type { IronSessionOptions } from "iron-session";
|
||||
import type { User } from "pages/api/user";
|
||||
|
||||
export const sessionOptions: IronSessionOptions = {
|
||||
password: process.env.SECRET_COOKIE_PASSWORD as string,
|
||||
cookieName: "iron-session/examples/next.js",
|
||||
// secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
|
||||
cookieOptions: {
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
},
|
||||
};
|
||||
|
||||
// This is where we specify the typings of req.session.*
|
||||
declare module "iron-session" {
|
||||
interface IronSessionData {
|
||||
user?: User;
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import useSWR from "swr";
|
||||
import type { User } from "pages/api/user";
|
||||
import type { Events } from "pages/api/events";
|
||||
|
||||
export default function useEvents(user: User | undefined) {
|
||||
// We do a request to /api/events only if the user is logged in
|
||||
const { data: events } = useSWR<Events>(
|
||||
user?.isLoggedIn ? `/api/events` : null,
|
||||
);
|
||||
|
||||
return { events };
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
import Router from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { User } from "pages/api/user";
|
||||
|
||||
export default function useUser({
|
||||
redirectTo = "",
|
||||
redirectIfFound = false,
|
||||
} = {}) {
|
||||
const { data: user, mutate: mutateUser } = useSWR<User>("/api/user");
|
||||
|
||||
useEffect(() => {
|
||||
// if no redirect needed, just return (example: already on /dashboard)
|
||||
// if user data not yet there (fetch in progress, logged in or not) then don't do anything yet
|
||||
if (!redirectTo || !user) return;
|
||||
|
||||
if (
|
||||
// If redirectTo is set, redirect if the user was not found.
|
||||
(redirectTo && !redirectIfFound && !user?.isLoggedIn) ||
|
||||
// If redirectIfFound is also set, redirect if the user was found
|
||||
(redirectIfFound && user?.isLoggedIn)
|
||||
) {
|
||||
Router.push(redirectTo);
|
||||
}
|
||||
}, [user, redirectIfFound, redirectTo]);
|
||||
|
||||
return { user, mutateUser };
|
||||
}
|
5
examples/with-iron-session/next-env.d.ts
vendored
5
examples/with-iron-session/next-env.d.ts
vendored
|
@ -1,5 +0,0 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
|
@ -1,13 +0,0 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
module.exports = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "avatars.githubusercontent.com",
|
||||
port: "",
|
||||
pathname: "/my-account/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"dev": "next dev",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"iron-session": "latest",
|
||||
"next": "latest",
|
||||
"octokit": "^1.7.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"swr": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/types": "^6.34.0",
|
||||
"@types/react": "^17.0.34",
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { AppProps } from "next/app";
|
||||
import { SWRConfig } from "swr";
|
||||
import fetchJson from "lib/fetchJson";
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<SWRConfig
|
||||
value={{
|
||||
fetcher: fetchJson,
|
||||
onError: (err) => {
|
||||
console.error(err);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SWRConfig>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
|
@ -1,33 +0,0 @@
|
|||
import { withIronSessionApiRoute } from "iron-session/next";
|
||||
import { sessionOptions } from "lib/session";
|
||||
import { Octokit } from "octokit";
|
||||
|
||||
import type { Endpoints } from "@octokit/types";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export type Events =
|
||||
Endpoints["GET /users/{username}/events"]["response"]["data"];
|
||||
|
||||
const octokit = new Octokit();
|
||||
|
||||
async function eventsRoute(req: NextApiRequest, res: NextApiResponse<Events>) {
|
||||
const user = req.session.user;
|
||||
|
||||
if (!user || user.isLoggedIn === false) {
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: events } =
|
||||
await octokit.rest.activity.listPublicEventsForUser({
|
||||
username: user.login,
|
||||
});
|
||||
|
||||
res.json(events);
|
||||
} catch (error) {
|
||||
res.status(200).json([]);
|
||||
}
|
||||
}
|
||||
|
||||
export default withIronSessionApiRoute(eventsRoute, sessionOptions);
|
|
@ -1,26 +0,0 @@
|
|||
import type { User } from "./user";
|
||||
|
||||
import { Octokit } from "octokit";
|
||||
import { withIronSessionApiRoute } from "iron-session/next";
|
||||
import { sessionOptions } from "lib/session";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
const octokit = new Octokit();
|
||||
|
||||
async function loginRoute(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { username } = await req.body;
|
||||
|
||||
try {
|
||||
const {
|
||||
data: { login, avatar_url },
|
||||
} = await octokit.rest.users.getByUsername({ username });
|
||||
|
||||
const user = { isLoggedIn: true, login, avatarUrl: avatar_url } as User;
|
||||
req.session.user = user;
|
||||
await req.session.save();
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: (error as Error).message });
|
||||
}
|
||||
}
|
||||
|
||||
export default withIronSessionApiRoute(loginRoute, sessionOptions);
|
|
@ -1,11 +0,0 @@
|
|||
import { withIronSessionApiRoute } from "iron-session/next";
|
||||
import { sessionOptions } from "lib/session";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import type { User } from "pages/api/user";
|
||||
|
||||
function logoutRoute(req: NextApiRequest, res: NextApiResponse<User>) {
|
||||
req.session.destroy();
|
||||
res.json({ isLoggedIn: false, login: "", avatarUrl: "" });
|
||||
}
|
||||
|
||||
export default withIronSessionApiRoute(logoutRoute, sessionOptions);
|
|
@ -1,28 +0,0 @@
|
|||
import { withIronSessionApiRoute } from "iron-session/next";
|
||||
import { sessionOptions } from "lib/session";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export type User = {
|
||||
isLoggedIn: boolean;
|
||||
login: string;
|
||||
avatarUrl: string;
|
||||
};
|
||||
|
||||
async function userRoute(req: NextApiRequest, res: NextApiResponse<User>) {
|
||||
if (req.session.user) {
|
||||
// in a real world application you might read the user id from the session and then do a database request
|
||||
// to get more information on the user if needed
|
||||
res.json({
|
||||
...req.session.user,
|
||||
isLoggedIn: true,
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
isLoggedIn: false,
|
||||
login: "",
|
||||
avatarUrl: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default withIronSessionApiRoute(userRoute, sessionOptions);
|
|
@ -1,59 +0,0 @@
|
|||
import Layout from "components/Layout";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>
|
||||
<span style={{ marginRight: ".3em", verticalAlign: "middle" }}>
|
||||
<Image src="/GitHub-Mark-32px.png" width="32" height="32" alt="" />
|
||||
</span>
|
||||
<a href="https://github.com/vvo/iron-session">iron-session</a> -
|
||||
Authentication example
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
This example creates an authentication system that uses a{" "}
|
||||
<b>signed and encrypted cookie to store session data</b>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It uses current best practices as for authentication in the Next.js
|
||||
ecosystem:
|
||||
<br />
|
||||
1. <b>no `getInitialProps`</b> to ensure every page is static
|
||||
<br />
|
||||
2. <b>`useUser` hook</b> together with `
|
||||
<a href="https://swr.vercel.app/">swr`</a> for data fetching
|
||||
</p>
|
||||
|
||||
<h2>Features</h2>
|
||||
|
||||
<ul>
|
||||
<li>Logged in status synchronized between browser windows/tabs</li>
|
||||
<li>Layout based on logged in status</li>
|
||||
<li>All pages are static</li>
|
||||
<li>Session data is signed and encrypted in a cookie</li>
|
||||
</ul>
|
||||
|
||||
<h2>Steps to test the functionality:</h2>
|
||||
|
||||
<ol>
|
||||
<li>Click login and enter your GitHub username.</li>
|
||||
<li>
|
||||
Click home and click profile again, notice how your session is being
|
||||
used through a token stored in a cookie.
|
||||
</li>
|
||||
<li>
|
||||
Click logout and try to go to profile again. You'll get
|
||||
redirected to the `/login` route.
|
||||
</li>
|
||||
</ol>
|
||||
<style jsx>{`
|
||||
li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`}</style>
|
||||
</Layout>
|
||||
);
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import useUser from "lib/useUser";
|
||||
import Layout from "components/Layout";
|
||||
import Form from "components/Form";
|
||||
import fetchJson, { FetchError } from "lib/fetchJson";
|
||||
|
||||
export default function Login() {
|
||||
// here we just check if user is already logged in and redirect to profile
|
||||
const { mutateUser } = useUser({
|
||||
redirectTo: "/profile-sg",
|
||||
redirectIfFound: true,
|
||||
});
|
||||
|
||||
const [errorMsg, setErrorMsg] = useState("");
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="login">
|
||||
<Form
|
||||
errorMessage={errorMsg}
|
||||
onSubmit={async function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const body = {
|
||||
username: event.currentTarget.username.value,
|
||||
};
|
||||
|
||||
try {
|
||||
mutateUser(
|
||||
await fetchJson("/api/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof FetchError) {
|
||||
setErrorMsg(error.data.message);
|
||||
} else {
|
||||
console.error("An unexpected error happened:", error);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.login {
|
||||
max-width: 21rem;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
`}</style>
|
||||
</Layout>
|
||||
);
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import React from "react";
|
||||
import Layout from "components/Layout";
|
||||
import useUser from "lib/useUser";
|
||||
import useEvents from "lib/useEvents";
|
||||
|
||||
// Make sure to check https://nextjs.org/docs/basic-features/layouts for more info on how to use layouts
|
||||
export default function SgProfile() {
|
||||
const { user } = useUser({
|
||||
redirectTo: "/login",
|
||||
});
|
||||
const { events } = useEvents(user);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Your GitHub profile</h1>
|
||||
<h2>
|
||||
This page uses{" "}
|
||||
<a href="https://nextjs.org/docs/basic-features/pages#static-generation-recommended">
|
||||
Static Generation (SG)
|
||||
</a>{" "}
|
||||
and the <a href="/api/user">/api/user</a> route (using{" "}
|
||||
<a href="https://github.com/vercel/swr">vercel/SWR</a>)
|
||||
</h2>
|
||||
{user && (
|
||||
<>
|
||||
<p style={{ fontStyle: "italic" }}>
|
||||
Public data, from{" "}
|
||||
<a href={`https://github.com/${user.login}`}>
|
||||
https://github.com/{user.login}
|
||||
</a>
|
||||
, reduced to `login` and `avatar_url`.
|
||||
</p>
|
||||
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)}
|
||||
|
||||
{events !== undefined && (
|
||||
<p>
|
||||
Number of GitHub events for user: <b>{events.length}</b>.{" "}
|
||||
{events.length > 0 && (
|
||||
<>
|
||||
Last event type: <b>{events[0].type}</b>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
import React from "react";
|
||||
import Layout from "components/Layout";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import { sessionOptions } from "lib/session";
|
||||
import { User } from "pages/api/user";
|
||||
|
||||
import { InferGetServerSidePropsType } from "next";
|
||||
|
||||
export default function SsrProfile({
|
||||
user,
|
||||
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Your GitHub profile</h1>
|
||||
<h2>
|
||||
This page uses{" "}
|
||||
<a href="https://nextjs.org/docs/basic-features/pages#server-side-rendering">
|
||||
Server-side Rendering (SSR)
|
||||
</a>{" "}
|
||||
and{" "}
|
||||
<a href="https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props">
|
||||
getServerSideProps
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
{user?.isLoggedIn && (
|
||||
<>
|
||||
<p style={{ fontStyle: "italic" }}>
|
||||
Public data, from{" "}
|
||||
<a href={`https://github.com/${user.login}`}>
|
||||
https://github.com/{user.login}
|
||||
</a>
|
||||
, reduced to `login` and `avatar_url`.
|
||||
</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async function ({
|
||||
req,
|
||||
res,
|
||||
}) {
|
||||
const user = req.session.user;
|
||||
|
||||
if (user === undefined) {
|
||||
res.setHeader("location", "/login");
|
||||
res.statusCode = 302;
|
||||
res.end();
|
||||
return {
|
||||
props: {
|
||||
user: { isLoggedIn: false, login: "", avatarUrl: "" } as User,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
props: { user: req.session.user },
|
||||
};
|
||||
},
|
||||
sessionOptions);
|
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"SECRET_COOKIE_PASSWORD": "2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8"
|
||||
},
|
||||
"build": {
|
||||
"env": {
|
||||
"SECRET_COOKIE_PASSWORD": "2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8"
|
||||
}
|
||||
},
|
||||
"github": {
|
||||
"silent": true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue