├── .gitignore
├── README.md
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-ssr.js
├── lib
└── apolloClient.ts
├── package-lock.json
├── package.json
└── src
├── components
├── AuthContent.tsx
├── CreatePostForm.tsx
├── Header.tsx
├── Layout.tsx
├── LogInForm.tsx
├── Nav.tsx
├── ProfileForm.tsx
├── SendPasswordResetEmailForm.tsx
├── SetPasswordForm.tsx
├── SignUpForm.tsx
└── UnAuthContent.tsx
├── hooks
└── useAuth.tsx
├── images
└── icon.png
├── pages
├── 404.tsx
├── create-post.tsx
├── forgot-password.tsx
├── index.tsx
├── log-in.tsx
├── log-out.tsx
├── members.tsx
├── profile.tsx
├── set-password.tsx
└── sign-up.tsx
└── styles
└── global.css
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .cache/
3 | public
4 | .env*
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Headless WordPress Authentication with Native Cookies - Gatsby
2 |
3 | This Gatsby app is a port of the Next.js app codebase from this blog post: https://developers.wpengine.com/blog/headless-wordpress-authentication-native-cookies/
4 |
5 | This app shows how to authenticate users using WordPress' own native auth cookies.
6 |
7 | ## Steps to Use:
8 |
9 | 1. Clone down this repo
10 | 1. Create a `.env.development` file inside of the app’s root folder. Open that file in a text editor and paste in `GATSBY_WORDPRESS_API_URL=https://headlesswpcookieauth.local/graphql`, replacing `headlesswpcookieauth.local` with the domain for your local WordPress site. This is the endpoint that Apollo Client will use when it sends requests to your WordPress backend.
11 | 1. Run `npm install` (or `yarn`) to install the app’s NPM dependencies.
12 | 1. Run `npm run develop` to get the server running locally.
13 | 1. You should now be able to visit http://localhost:8000/ in a web browser and see the app’s homepage.
14 |
15 | See the blog post linked above for details on how to set up the WordPress backend, and for a tour of the app's features.
16 |
--------------------------------------------------------------------------------
/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { ApolloProvider } from "@apollo/client";
3 |
4 | import { client } from "./lib/apolloClient";
5 | import { AuthProvider } from "./src/hooks/useAuth";
6 | import "./src/styles/global.css";
7 |
8 | export const wrapRootElement = ({ element }) => (
9 |
10 | {element}
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/gatsby-config.js:
--------------------------------------------------------------------------------
1 | require("dotenv").config({
2 | path: `.env.${process.env.NODE_ENV}`,
3 | });
4 |
5 | module.exports = {
6 | siteMetadata: {
7 | title: "headless-wp-cookie-auth-gatsby",
8 | },
9 | plugins: [
10 | {
11 | resolve: "gatsby-source-wordpress",
12 | options: {
13 | url: process.env.GATSBY_WORDPRESS_API_URL,
14 | },
15 | },
16 | ],
17 | };
18 |
--------------------------------------------------------------------------------
/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | import "./src/styles/global.css";
2 |
3 | export { wrapRootElement } from "./gatsby-browser";
4 |
--------------------------------------------------------------------------------
/lib/apolloClient.ts:
--------------------------------------------------------------------------------
1 | import fetch from "cross-fetch";
2 | import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
3 |
4 | const link = createHttpLink({
5 | uri: process.env.GATSBY_WORDPRESS_API_URL,
6 | credentials: 'include',
7 | fetch,
8 | });
9 |
10 | export const client = new ApolloClient({
11 | cache: new InMemoryCache(),
12 | link,
13 | });
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "headless-wp-cookie-auth-gatsby",
3 | "version": "1.0.0",
4 | "private": true,
5 | "description": "headless-wp-cookie-auth-gatsby",
6 | "author": "Kellen Mace",
7 | "keywords": [
8 | "gatsby"
9 | ],
10 | "scripts": {
11 | "develop": "gatsby develop",
12 | "start": "gatsby develop",
13 | "build": "gatsby build",
14 | "serve": "gatsby serve",
15 | "clean": "gatsby clean"
16 | },
17 | "dependencies": {
18 | "@apollo/client": "^3.3.20",
19 | "cross-fetch": "^3.1.4",
20 | "dotenv": "^8.6.0",
21 | "gatsby": "^3.6.2",
22 | "gatsby-plugin-sharp": "^3.8.0",
23 | "gatsby-source-wordpress": "^5.8.0",
24 | "gatsby-transformer-sharp": "^3.8.0",
25 | "react": "^17.0.1",
26 | "react-dom": "^17.0.1"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/AuthContent.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useEffect, ReactNode } from "react";
3 | import { navigate } from "gatsby";
4 |
5 | import useAuth from "../hooks/useAuth";
6 |
7 | export default function AuthContent({ children }: { children: ReactNode }) {
8 | const { loggedIn, loading } = useAuth();
9 |
10 | // Navigate unauthenticated users to Log In page.
11 | useEffect(() => {
12 | if (!loading && !loggedIn) {
13 | navigate('/log-in');
14 | }
15 | }, [loggedIn, loading, navigate]);
16 |
17 | if (loggedIn) {
18 | return <>{children}>;
19 | }
20 |
21 | return
Loading...
;
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/CreatePostForm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useMutation, gql } from "@apollo/client";
3 |
4 | const CREATE_POST = gql`
5 | mutation createPost($title: String!, $content: String!) {
6 | createPost(input: {
7 | title: $title
8 | content: $content
9 | status: PUBLISH
10 | }) {
11 | post {
12 | databaseId
13 | }
14 | }
15 | }
16 | `;
17 |
18 | export default function CreatePostForm() {
19 | const [createPost, { data, loading, error }] = useMutation(CREATE_POST);
20 | const wasPostCreated = Boolean(data?.createPost?.post?.databaseId);
21 |
22 | function handleSubmit(event: React.FormEvent) {
23 | event.preventDefault();
24 | const data = new FormData(event.currentTarget);
25 | const values = Object.fromEntries(data);
26 | createPost({
27 | variables: values
28 | }).catch(error => {
29 | console.error(error);
30 | });
31 | }
32 |
33 | if (wasPostCreated) {
34 | return (
35 | Post successfully created.
36 | );
37 | }
38 |
39 | return (
40 |
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Nav from "./Nav";
4 |
5 | export default function Header() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Layout.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { ReactNode } from "react";
3 |
4 | import Header from "./Header";
5 |
6 | export default function Layout({ children }: { children: ReactNode }) {
7 | return (
8 | <>
9 | Headless WP App
10 |
11 | {children}
12 | >
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/LogInForm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Link } from "gatsby";
3 | import { useMutation, gql } from "@apollo/client";
4 |
5 | import { GET_USER } from "../hooks/useAuth";
6 |
7 | const LOG_IN = gql`
8 | mutation logIn($login: String!, $password: String!) {
9 | loginWithCookies(input: {
10 | login: $login
11 | password: $password
12 | }) {
13 | status
14 | }
15 | }
16 | `;
17 |
18 | export default function LogInForm() {
19 | const [logIn, { loading, error }] = useMutation(LOG_IN, {
20 | refetchQueries: [
21 | { query: GET_USER }
22 | ],
23 | });
24 | const errorMessage = error?.message || '';
25 | const isEmailValid =
26 | !errorMessage.includes('empty_email') &&
27 | !errorMessage.includes('empty_username') &&
28 | !errorMessage.includes('invalid_email') &&
29 | !errorMessage.includes('invalid_username');
30 | const isPasswordValid =
31 | !errorMessage.includes('empty_password') &&
32 | !errorMessage.includes('incorrect_password');
33 |
34 | function handleSubmit(event: React.FormEvent) {
35 | event.preventDefault();
36 | const data = new FormData(event.currentTarget);
37 | const { email, password } = Object.fromEntries(data);
38 | logIn({
39 | variables: {
40 | login: email,
41 | password,
42 | }
43 | }).catch(error => {
44 | console.error(error);
45 | });
46 | }
47 |
48 | return (
49 |
87 | );
88 | }
89 |
--------------------------------------------------------------------------------
/src/components/Nav.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Link } from "gatsby";
3 |
4 | import useAuth from "../hooks/useAuth";
5 |
6 | export default function Nav() {
7 | const { loggedIn } = useAuth();
8 |
9 | return (
10 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/src/components/ProfileForm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useMutation, gql } from "@apollo/client";
3 |
4 | import useAuth, { User } from "../hooks/useAuth";
5 |
6 | const UPDATE_PROFILE = gql`
7 | mutation updateProfile(
8 | $id: ID!
9 | $firstName: String!,
10 | $lastName: String!,
11 | $email: String!
12 | ) {
13 | updateUser(input: {
14 | id: $id
15 | firstName: $firstName
16 | lastName: $lastName
17 | email: $email
18 | }) {
19 | user {
20 | databaseId
21 | }
22 | }
23 | }
24 | `;
25 |
26 | export default function ProfileForm() {
27 | const { user } = useAuth();
28 | const { id, firstName, lastName, email } = user as User;
29 | const [updateProfile, { data, loading, error }] = useMutation(UPDATE_PROFILE);
30 | const wasProfileUpdated = Boolean(data?.updateUser?.user?.databaseId);
31 |
32 | function handleSubmit(event: React.FormEvent) {
33 | event.preventDefault();
34 | const data = new FormData(event.currentTarget);
35 | const values = Object.fromEntries(data);
36 | updateProfile({
37 | variables: { id, ...values, },
38 | }).catch(error => {
39 | console.error(error);
40 | });
41 | }
42 |
43 | return (
44 |
83 | );
84 | }
85 |
--------------------------------------------------------------------------------
/src/components/SendPasswordResetEmailForm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useMutation, gql } from "@apollo/client";
3 |
4 | const SEND_PASSWORD_RESET_EMAIL = gql`
5 | mutation sendPasswordResetEmail($username: String!) {
6 | sendPasswordResetEmail(
7 | input: { username: $username }
8 | ) {
9 | user {
10 | databaseId
11 | }
12 | }
13 | }
14 | `;
15 |
16 | export default function SendPasswordResetEmailForm() {
17 | const [sendPasswordResetEmail, { loading, error, data }] = useMutation(
18 | SEND_PASSWORD_RESET_EMAIL
19 | );
20 | const wasEmailSent = Boolean(data?.sendPasswordResetEmail?.user?.databaseId);
21 |
22 | function handleSubmit(event: React.FormEvent) {
23 | event.preventDefault();
24 | const data = new FormData(event.currentTarget);
25 | const { email } = Object.fromEntries(data);
26 | sendPasswordResetEmail({
27 | variables: {
28 | username: email,
29 | }
30 | }).catch(error => {
31 | console.error(error);
32 | });
33 | }
34 |
35 | if (wasEmailSent) {
36 | return (
37 | Please check your email. A password reset link has been sent to you.
38 | );
39 | }
40 |
41 | return (
42 |
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/SetPasswordForm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useState } from "react";
3 | import { useMutation, gql } from "@apollo/client";
4 | import { Link } from "gatsby";
5 |
6 | const RESET_PASSWORD = gql`
7 | mutation resetUserPassword(
8 | $key: String!
9 | $login: String!
10 | $password: String!
11 | ) {
12 | resetUserPassword(
13 | input: {
14 | key: $key
15 | login: $login
16 | password: $password
17 | }
18 | ) {
19 | user {
20 | databaseId
21 | }
22 | }
23 | }
24 | `;
25 |
26 | interface Props {
27 | resetKey: string;
28 | login: string;
29 | }
30 |
31 | export default function SetPasswordForm({ resetKey: key, login }: Props) {
32 | const [password, setPassword] = useState('');
33 | const [passwordConfirm, setPasswordConfirm] = useState('');
34 | const [clientErrorMessage, setClientErrorMessage] = useState('');
35 | const [resetPassword, { data, loading, error }] = useMutation(RESET_PASSWORD);
36 | const wasPasswordReset = Boolean(data?.resetUserPassword?.user?.databaseId);
37 |
38 | function handleSubmit(event: React.FormEvent) {
39 | event.preventDefault()
40 | const isValid = validate();
41 | if (!isValid) return
42 |
43 | resetPassword({
44 | variables: {
45 | key,
46 | login,
47 | password,
48 | },
49 | }).catch(error => {
50 | console.error(error);
51 | });
52 | }
53 |
54 | function validate() {
55 | setClientErrorMessage('');
56 |
57 | const isPasswordLongEnough = password.length >= 5;
58 | if (!isPasswordLongEnough) {
59 | setClientErrorMessage('Password must be at least 5 characters.');
60 | return false;
61 | }
62 |
63 | const doPasswordsMatch = password === passwordConfirm;
64 | if (!doPasswordsMatch) {
65 | setClientErrorMessage('Passwords must match.');
66 | return false;
67 | }
68 |
69 | return true;
70 | }
71 |
72 | if (wasPasswordReset) {
73 | return (
74 | <>
75 | Your new password has been set.
76 |
77 | Log in
78 |
79 | >
80 | );
81 | }
82 |
83 | return (
84 |
115 | );
116 | }
117 |
--------------------------------------------------------------------------------
/src/components/SignUpForm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useMutation, gql } from "@apollo/client";
3 | import { Link } from "gatsby";
4 |
5 | const REGISTER_USER = gql`
6 | mutation registerUser(
7 | $email: String!
8 | $firstName: String!
9 | $lastName: String!
10 | ) {
11 | registerUser(
12 | input: {
13 | username: $email
14 | email: $email
15 | firstName: $firstName
16 | lastName: $lastName
17 | }
18 | ) {
19 | user {
20 | databaseId
21 | }
22 | }
23 | }
24 | `;
25 |
26 | export default function SignUpForm() {
27 | const [register, { data, loading, error }] = useMutation(REGISTER_USER);
28 | const wasSignUpSuccessful = Boolean(data?.registerUser?.user?.databaseId);
29 |
30 | function handleSubmit(event: React.FormEvent) {
31 | event.preventDefault();
32 | const data = new FormData(event.currentTarget);
33 | const values = Object.fromEntries(data);
34 | register({
35 | variables: values,
36 | }).catch(error => {
37 | console.error(error);
38 | });
39 | }
40 |
41 | if (wasSignUpSuccessful) {
42 | return (
43 |
44 | Thanks! Check your email – an account confirmation link has been sent to you.
45 |
46 | )
47 | }
48 |
49 | return (
50 |
93 | );
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/UnAuthContent.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useEffect, ReactNode } from "react";
3 | import { navigate } from "gatsby";
4 |
5 | import useAuth from "../hooks/useAuth";
6 |
7 | export default function UnAuthContent({ children }: { children: ReactNode }) {
8 | const { loggedIn, loading } = useAuth();
9 |
10 | // Navigate authenticated users to Members page.
11 | useEffect(() => {
12 | if (!loading && loggedIn) {
13 | navigate('/members');
14 | }
15 | }, [loggedIn, loading, navigate]);
16 |
17 | if (!loggedIn) {
18 | return <>{children}>;
19 | }
20 |
21 | return Loading...
;
22 | }
23 |
--------------------------------------------------------------------------------
/src/hooks/useAuth.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery, gql, ApolloError } from "@apollo/client";
2 | import React, { createContext, useContext, ReactNode } from "react";
3 |
4 | export interface User {
5 | id: string;
6 | databaseId: number;
7 | firstName: string;
8 | lastName: string;
9 | email: string;
10 | capabilities: string[];
11 | }
12 |
13 | interface AuthData {
14 | loggedIn: boolean;
15 | user?: User,
16 | loading: boolean;
17 | error?: ApolloError;
18 | }
19 |
20 | const DEFAULT_STATE: AuthData = {
21 | loggedIn: false,
22 | user: undefined,
23 | loading: false,
24 | error: undefined,
25 | };
26 |
27 | const AuthContext = createContext(DEFAULT_STATE);
28 |
29 | export const GET_USER = gql`
30 | query getUser {
31 | viewer {
32 | id
33 | databaseId
34 | firstName
35 | lastName
36 | email
37 | capabilities
38 | }
39 | }
40 | `;
41 |
42 | export function AuthProvider({ children }: { children: ReactNode }) {
43 | const { data, loading, error } = useQuery(GET_USER);
44 | const user = data?.viewer;
45 | const loggedIn = Boolean(user);
46 |
47 | const value = {
48 | loggedIn,
49 | user,
50 | loading,
51 | error,
52 | };
53 |
54 | return {children};
55 | }
56 |
57 | const useAuth = () => useContext(AuthContext);
58 |
59 | export default useAuth;
60 |
--------------------------------------------------------------------------------
/src/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kellenmace/headless-wordpress-authentication-native-cookies-gatsby/42eaee2e34ab6990dcf88bcfd9c66a52cdc767c8/src/images/icon.png
--------------------------------------------------------------------------------
/src/pages/404.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Link } from "gatsby";
3 |
4 | // styles
5 | const pageStyles = {
6 | color: "#232129",
7 | padding: "96px",
8 | fontFamily: "-apple-system, Roboto, sans-serif, serif",
9 | };
10 | const headingStyles = {
11 | marginTop: 0,
12 | marginBottom: 64,
13 | maxWidth: 320,
14 | };
15 |
16 | const paragraphStyles = {
17 | marginBottom: 48,
18 | };
19 | const codeStyles = {
20 | color: "#8A6534",
21 | padding: 4,
22 | backgroundColor: "#FFF4DB",
23 | fontSize: "1.25rem",
24 | borderRadius: 4,
25 | };
26 |
27 | // markup
28 | const NotFoundPage = () => {
29 | return (
30 |
31 | Not found
32 | Page not found
33 |
34 | Sorry{" "}
35 |
36 | 😔
37 | {" "}
38 | we couldn’t find what you were looking for.
39 |
40 | {process.env.NODE_ENV === "development" ? (
41 | <>
42 |
43 | Try creating a page in src/pages/
.
44 |
45 | >
46 | ) : null}
47 |
48 | Go home.
49 |
50 |
51 | );
52 | };
53 |
54 | export default NotFoundPage;
55 |
--------------------------------------------------------------------------------
/src/pages/create-post.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import useAuth from "../hooks/useAuth";
4 | import AuthContent from "../components/AuthContent";
5 | import Layout from "../components/Layout";
6 | import CreatePostForm from "../components/CreatePostForm";
7 |
8 | export default function CreatePost() {
9 | const { user } = useAuth();
10 | const canCreatePosts = Boolean(user?.capabilities?.includes('publish_posts'));
11 |
12 | return (
13 |
14 |
15 | Create Post
16 | {canCreatePosts ? (
17 |
18 | ) : (
19 | You don't have the permissions necessary to create posts.
20 | )}
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/forgot-password.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Layout from "../components/Layout";
4 | import UnAuthContent from "../components/UnAuthContent";
5 | import SendPasswordResetEmailForm from "../components/SendPasswordResetEmailForm";
6 |
7 | export default function ForgotPassword() {
8 | return (
9 |
10 | Forgot Your Password?
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Layout from "../components/Layout";
4 |
5 | // styles
6 | const pageStyles = {
7 | color: "#232129",
8 | padding: 96,
9 | fontFamily: "-apple-system, Roboto, sans-serif, serif",
10 | }
11 | const headingStyles = {
12 | marginTop: 0,
13 | marginBottom: 64,
14 | maxWidth: 320,
15 | }
16 | const headingAccentStyles = {
17 | color: "#663399",
18 | }
19 | const paragraphStyles = {
20 | marginBottom: 48,
21 | }
22 | const codeStyles = {
23 | color: "#8A6534",
24 | padding: 4,
25 | backgroundColor: "#FFF4DB",
26 | fontSize: "1.25rem",
27 | borderRadius: 4,
28 | }
29 | const listStyles = {
30 | marginBottom: 96,
31 | paddingLeft: 0,
32 | }
33 | const listItemStyles = {
34 | fontWeight: 300,
35 | fontSize: 24,
36 | maxWidth: 560,
37 | marginBottom: 30,
38 | }
39 |
40 | const linkStyle = {
41 | color: "#8954A8",
42 | fontWeight: "bold",
43 | fontSize: 16,
44 | verticalAlign: "5%",
45 | }
46 |
47 | const docLinkStyle = {
48 | ...linkStyle,
49 | listStyleType: "none",
50 | marginBottom: 24,
51 | }
52 |
53 | const descriptionStyle = {
54 | color: "#232129",
55 | fontSize: 14,
56 | marginTop: 10,
57 | marginBottom: 0,
58 | lineHeight: 1.25,
59 | }
60 |
61 | const docLink = {
62 | text: "Documentation",
63 | url: "https://www.gatsbyjs.com/docs/",
64 | color: "#8954A8",
65 | }
66 |
67 | const badgeStyle = {
68 | color: "#fff",
69 | backgroundColor: "#088413",
70 | border: "1px solid #088413",
71 | fontSize: 11,
72 | fontWeight: "bold",
73 | letterSpacing: 1,
74 | borderRadius: 4,
75 | padding: "4px 6px",
76 | display: "inline-block",
77 | position: "relative",
78 | top: -2,
79 | marginLeft: 10,
80 | lineHeight: 1,
81 | }
82 |
83 | // data
84 | const links = [
85 | {
86 | text: "Tutorial",
87 | url: "https://www.gatsbyjs.com/docs/tutorial/",
88 | description:
89 | "A great place to get started if you're new to web development. Designed to guide you through setting up your first Gatsby site.",
90 | color: "#E95800",
91 | },
92 | {
93 | text: "How to Guides",
94 | url: "https://www.gatsbyjs.com/docs/how-to/",
95 | description:
96 | "Practical step-by-step guides to help you achieve a specific goal. Most useful when you're trying to get something done.",
97 | color: "#1099A8",
98 | },
99 | {
100 | text: "Reference Guides",
101 | url: "https://www.gatsbyjs.com/docs/reference/",
102 | description:
103 | "Nitty-gritty technical descriptions of how Gatsby works. Most useful when you need detailed information about Gatsby's APIs.",
104 | color: "#BC027F",
105 | },
106 | {
107 | text: "Conceptual Guides",
108 | url: "https://www.gatsbyjs.com/docs/conceptual/",
109 | description:
110 | "Big-picture explanations of higher-level Gatsby concepts. Most useful for building understanding of a particular topic.",
111 | color: "#0D96F2",
112 | },
113 | {
114 | text: "Plugin Library",
115 | url: "https://www.gatsbyjs.com/plugins",
116 | description:
117 | "Add functionality and customize your Gatsby site or app with thousands of plugins built by our amazing developer community.",
118 | color: "#8EB814",
119 | },
120 | {
121 | text: "Build and Host",
122 | url: "https://www.gatsbyjs.com/cloud",
123 | badge: true,
124 | description:
125 | "Now you’re ready to show the world! Give your Gatsby site superpowers: Build and host on Gatsby Cloud. Get started for free!",
126 | color: "#663399",
127 | },
128 | ]
129 |
130 | // markup
131 | const IndexPage = () => {
132 | return (
133 |
134 |
135 |
Home Page
136 |
137 | Congratulations
138 |
139 | — you just made a Gatsby site!
140 |
141 | 🎉🎉🎉
142 |
143 |
144 |
145 | Edit src/pages/index.js
to see this page
146 | update in real-time.{" "}
147 |
148 | 😎
149 |
150 |
151 |
179 |

183 |
184 |
185 | )
186 | }
187 |
188 | export default IndexPage
189 |
--------------------------------------------------------------------------------
/src/pages/log-in.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Layout from "../components/Layout";
4 | import UnAuthContent from "../components/UnAuthContent";
5 | import LogInForm from "../components/LogInForm";
6 |
7 | export default function LogIn() {
8 | return (
9 |
10 | Log In
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/log-out.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { useEffect } from "react";
3 | import { useMutation, gql } from "@apollo/client";
4 |
5 | import { GET_USER } from "../hooks/useAuth";
6 | import Layout from "../components/Layout";
7 |
8 | const LOG_OUT = gql`
9 | mutation logOut {
10 | logout(input: {}) {
11 | status
12 | }
13 | }
14 | `;
15 |
16 | export default function LogOut() {
17 | const [logOut, { called, loading, error, data }] = useMutation(LOG_OUT, {
18 | refetchQueries: [
19 | { query: GET_USER }
20 | ],
21 | });
22 | const loggedOut = Boolean(data?.logout?.status);
23 |
24 | useEffect(() => {
25 | logOut();
26 | }, [logOut]);
27 |
28 | return (
29 |
30 | Log Out
31 | {!called || loading ? (
32 | Logging out...
33 | ) : error ? (
34 | {error.message}
35 | ) : !loggedOut ? (
36 | Unable to log out. Please reload the page and try again.
37 | ) : (
38 | You have been logged out.
39 | )}
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/src/pages/members.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Layout from "../components/Layout";
4 | import AuthContent from "../components/AuthContent";
5 |
6 | export default function MembersContent() {
7 | return (
8 |
9 | Members
10 |
11 | Here is some top-secret members-only content!
12 |
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/pages/profile.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import AuthContent from "../components/AuthContent";
4 | import Layout from "../components/Layout";
5 | import ProfileForm from "../components/ProfileForm";
6 |
7 | export default function Profile() {
8 | return (
9 |
10 | Profile
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/pages/set-password.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Layout from "../components/Layout";
4 | import SetPasswordForm from "../components/SetPasswordForm";
5 |
6 | export default function SetPassword() {
7 | const params = new URLSearchParams(window.location.search);
8 | const resetKey = params.get('key') || '';
9 | const login = params.get('login') || '';
10 |
11 | return (
12 |
13 | Set Password
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/pages/sign-up.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Layout from "../components/Layout";
4 | import UnAuthContent from "../components/UnAuthContent";
5 | import SignUpForm from "../components/SignUpForm";
6 |
7 | export default function SignUp() {
8 | return (
9 |
10 | Sign Up
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/src/styles/global.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --border-radius: 8px;
3 |
4 | /* Colors */
5 | --color-white: #fff;
6 | --color-gray-1: #102a43;
7 | --color-gray-2: #243b53;
8 | --color-gray-3: #334e68;
9 | --color-gray-4: #486581;
10 | --color-gray-5: #627d98;
11 | --color-gray-6: #829ab1;
12 | --color-gray-7: #9fb3c8;
13 | --color-gray-8: #bcccdc;
14 | --color-gray-9: #d9e2ec;
15 | --color-gray-10: #f0f4f8;
16 | --color-yellow-1: #8d2b0b;
17 | --color-yellow-2: #b44d12;
18 | --color-yellow-3: #cb6e17;
19 | --color-yellow-4: #de911d;
20 | --color-yellow-5: #f0b429;
21 | --color-yellow-6: #f7c948;
22 | --color-yellow-7: #fadb5f;
23 | --color-yellow-8: #fce588;
24 | --color-yellow-9: #fff3c4;
25 | --color-yellow-10: #fffbea;
26 |
27 | /* Box Shadows */
28 | --box-shadow-1: rgba(0, 0, 0, 0.2) 0px 2px 1px -1px,
29 | rgba(0, 0, 0, 0.14) 0px 1px 1px 0px, rgba(0, 0, 0, 0.12) 0px 1px 3px 0px;
30 | --box-shadow-2: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px,
31 | rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px;
32 | --box-shadow-3: rgba(0, 0, 0, 0.2) 0px 3px 3px -2px,
33 | rgba(0, 0, 0, 0.14) 0px 3px 4px 0px, rgba(0, 0, 0, 0.12) 0px 1px 8px 0px;
34 | --box-shadow-4: rgba(0, 0, 0, 0.2) 0px 2px 4px -1px,
35 | rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px;
36 | --box-shadow-5: rgba(0, 0, 0, 0.2) 0px 3px 5px -1px,
37 | rgba(0, 0, 0, 0.14) 0px 5px 8px 0px, rgba(0, 0, 0, 0.12) 0px 1px 14px 0px;
38 | --box-shadow-6: rgba(0, 0, 0, 0.2) 0px 3px 5px -1px,
39 | rgba(0, 0, 0, 0.14) 0px 6px 10px 0px, rgba(0, 0, 0, 0.12) 0px 1px 18px 0px;
40 | --box-shadow-7: rgba(0, 0, 0, 0.2) 0px 4px 5px -2px,
41 | rgba(0, 0, 0, 0.14) 0px 7px 10px 1px, rgba(0, 0, 0, 0.12) 0px 2px 16px 1px;
42 | --box-shadow-8: rgba(0, 0, 0, 0.2) 0px 5px 5px -3px,
43 | rgba(0, 0, 0, 0.14) 0px 8px 10px 1px, rgba(0, 0, 0, 0.12) 0px 3px 14px 2px;
44 | --box-shadow-9: rgba(0, 0, 0, 0.2) 0px 5px 6px -3px,
45 | rgba(0, 0, 0, 0.14) 0px 9px 12px 1px, rgba(0, 0, 0, 0.12) 0px 3px 16px 2px;
46 | --box-shadow-10: rgba(0, 0, 0, 0.2) 0px 6px 6px -3px,
47 | rgba(0, 0, 0, 0.14) 0px 10px 14px 1px, rgba(0, 0, 0, 0.12) 0px 4px 18px 3px;
48 | --box-shadow-11: rgba(0, 0, 0, 0.2) 0px 6px 7px -4px,
49 | rgba(0, 0, 0, 0.14) 0px 11px 15px 1px, rgba(0, 0, 0, 0.12) 0px 4px 20px 3px;
50 | --box-shadow-12: rgba(0, 0, 0, 0.2) 0px 7px 8px -4px,
51 | rgba(0, 0, 0, 0.14) 0px 12px 17px 2px, rgba(0, 0, 0, 0.12) 0px 5px 22px 4px;
52 | --box-shadow-13: rgba(0, 0, 0, 0.2) 0px 7px 8px -4px,
53 | rgba(0, 0, 0, 0.14) 0px 13px 19px 2px, rgba(0, 0, 0, 0.12) 0px 5px 24px 4px;
54 | --box-shadow-14: rgba(0, 0, 0, 0.2) 0px 7px 9px -4px,
55 | rgba(0, 0, 0, 0.14) 0px 14px 21px 2px, rgba(0, 0, 0, 0.12) 0px 5px 26px 4px;
56 | --box-shadow-15: rgba(0, 0, 0, 0.2) 0px 8px 9px -5px,
57 | rgba(0, 0, 0, 0.14) 0px 15px 22px 2px, rgba(0, 0, 0, 0.12) 0px 6px 28px 5px;
58 | }
59 |
60 | html,
61 | body {
62 | padding: 0;
63 | margin: 0;
64 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
65 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
66 | background-color: #f0f4f8;
67 | color: var(--color-gray-2);
68 | }
69 |
70 | h1 {
71 | font-size: 2.1rem;
72 | line-height: 1.2;
73 | }
74 | h2 {
75 | font-size: 1.7rem;
76 | line-height: 1.3;
77 | }
78 | h3 {
79 | font-size: 1.5rem;
80 | line-height: 1.4;
81 | }
82 | h4 {
83 | font-size: 1.3rem;
84 | line-height: 1.5;
85 | }
86 | h5 {
87 | font-size: 1rem;
88 | line-height: 1.6;
89 | }
90 | h6 {
91 | font-size: 0.8rem;
92 | line-height: 1.7;
93 | }
94 |
95 | a {
96 | color: inherit;
97 | }
98 |
99 | * {
100 | box-sizing: border-box;
101 | }
102 |
103 | main {
104 | margin: 0 auto 4rem;
105 | max-width: 1000px;
106 | padding: 4rem 1rem 0;
107 | }
108 |
109 | @media (min-width: 850px) {
110 | main {
111 | padding-left: 4rem;
112 | padding-right: 4rem;
113 | }
114 | }
115 |
116 | .header {
117 | border-bottom: 2px solid var(--color-gray-9);
118 | }
119 |
120 | .nav {
121 | margin: 0;
122 | padding: 0;
123 | display: flex;
124 | }
125 |
126 | .nav li {
127 | list-style: none;
128 | }
129 |
130 | .nav a {
131 | display: block;
132 | padding: 2rem;
133 | text-decoration: none;
134 | }
135 |
136 | .error-message {
137 | color: var(--color-yellow-1);
138 | }
139 |
140 | fieldset {
141 | border: none;
142 | margin: 0;
143 | padding: 0;
144 | }
145 |
146 | form label {
147 | display: block;
148 | font-size: 0.9rem;
149 | }
150 |
151 | form input,
152 | form textarea {
153 | border: 2px solid var(--color-gray-9);
154 | padding: 0.5rem;
155 | border-radius: var(--border-radius);
156 | margin: 0.1rem 0 1rem;
157 | width: 100%;
158 | max-width: 300px;
159 | }
160 |
161 | form input:focus {
162 | outline: none;
163 | border-color: var(--color-yellow-6);
164 | }
165 |
166 | button {
167 | display: block;
168 | font-weight: 700;
169 | border: none;
170 | border-radius: var(--border-radius);
171 | padding: 14px 18px;
172 | background-color: var(--color-yellow-6);
173 | color: var(--color-yellow-3);
174 | cursor: pointer;
175 | }
176 |
177 | button:hover,
178 | button:active {
179 | color: var(--color-yellow-2);
180 | }
181 |
182 | .profile-update-confirmation {
183 | display: block;
184 | background: var(--color-gray-9);
185 | border-radius: var(--border-radius);
186 | padding: 1rem;
187 | }
188 |
189 | .forgot-password-link {
190 | display: block;
191 | font-size: 0.7rem;
192 | margin-bottom: 1rem;
193 | }
194 |
195 | .account-sign-up-message {
196 | margin-top: 2.5rem;
197 | }
198 |
--------------------------------------------------------------------------------