├── .gitignore
├── README.md
├── components
├── ContextMenu.js
├── Footer.js
├── Header.js
├── Hero.js
├── Nav.js
├── Post.js
└── PostCard.js
├── fixtures.js
├── hooks
└── useUser.js
├── jsconfig.json
├── package.json
├── pages
├── _app.js
├── api
│ └── preview.js
├── edit
│ ├── [slug].js
│ └── new.js
├── index.js
├── login.js
└── posts
│ └── [slug].js
├── postcss.config.js
├── screenshot.png
├── slides.key
├── styles
└── index.css
├── tailwind.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # From Front-end to Full Stack – Next.js Conf 2020
2 |
3 | > This project demonstrates the architecture changes Amplify JS needed to support Next.js,
4 | > authentication & data-access in a client-side application,
5 | > and how to move logic to the server for better performance.
6 |
7 | 
8 |
9 | ## Setup
10 |
11 | 1. Clone this repo
12 |
13 | ```shell
14 | git clone git@github.com:ericclemmons/next.js-conf-2020.git
15 | ```
16 |
17 | 1. Change into the directory & install the dependencies
18 |
19 | ```shell
20 | cd next.js-conf-2020
21 |
22 | yarn install
23 | ```
24 |
25 | 1. **Next, choose your own adventure:**:
26 |
27 | 1. You can either continue with the [`main`](https://github.com/ericclemmons/next.js-conf-2020/tree/main) branch & setup Amplify from scratch (e.g. `amplify init`, `amplify add auth`, `amplify add api`).
28 | 1. Or, you can checkout the [`live-demo`](https://github.com/ericclemmons/next.js-conf-2020/tree/live-demo) branch and re-use the backend configuration from the presentation.
29 |
30 | ## New Backend ([`main`](https://github.com/ericclemmons/next.js-conf-2020/tree/main))
31 |
32 | 1. Initialize Amplify
33 |
34 | ```shell
35 | amplify init
36 | ```
37 |
38 |
39 |
40 | More details…
41 |
42 |
43 | ```console
44 | $ amplify init
45 | ? Enter a name for the project nextjsconf2020
46 | ? Enter a name for the environment dev
47 | ? Choose your default editor: Visual Studio Code
48 | ? Choose the type of app that you're building javascript
49 | Please tell us about your project
50 | ? What javascript framework are you using react
51 | ? Source Directory Path: src
52 | ? Distribution Directory Path: build
53 | ? Build Command: npm run-script build
54 | ? Start Command: npm run-script start
55 | ```
56 |
57 |
58 |
59 | 1. Add Authentication
60 |
61 | ```shell
62 | amplify add auth
63 | ```
64 |
65 |
66 |
67 | More details…
68 |
69 |
70 | ```console
71 | $ amplify add auth
72 | Do you want to use the default authentication and security configuration? Default configuration
73 | Warning: you will not be able to edit these selections.
74 | How do you want users to be able to sign in? Username
75 | Do you want to configure advanced settings? No, I am done.
76 | ```
77 |
78 |
79 |
80 | 1. Add API
81 |
82 | ```shell
83 | amplify add api
84 | ```
85 |
86 |
87 |
88 | More details…
89 |
90 |
91 | ```console
92 | $ amplify add api
93 | ? Please select from one of the below mentioned services: GraphQL
94 | ? Provide API name: nextjsconf2020
95 | ? Choose the default authorization type for the API API key
96 | ? Enter a description for the API key:
97 | ? After how many days from now the API key should expire (1-365): 7
98 | ? Do you want to configure advanced settings for the GraphQL API Yes, I want to make some additional changes.
99 | ? Configure additional auth types? Yes
100 | ? Choose the additional authorization types you want to configure for the API Amazon Cognito User Pool
101 | Cognito UserPool configuration
102 | Use a Cognito user pool configured as a part of this project.
103 | ? Configure conflict detection? No
104 | ? Do you have an annotated GraphQL schema? No
105 | ? Choose a schema template: Single object with fields (e.g., “Todo” with ID, name, description)
106 | ```
107 |
108 |
109 |
110 | Next, you'll be prompted to edit your schema:
111 |
112 | > ? Do you want to edit the schema now? **Yes**
113 |
114 | Replace the contents of your editor with the following, then save:
115 |
116 | ```gql
117 | type Post
118 | @model
119 | # See: https://docs.amplify.aws/cli/graphql-transformer/directives#owner-authorization
120 | @auth(rules: [{ allow: owner }, { allow: public, operations: [read] }])
121 | # See: https://docs.amplify.aws/cli/graphql-transformer/directives#how-to-use-key
122 | @key(name: "postsBySlug", fields: ["slug"], queryField: "postsBySlug") {
123 | id: ID!
124 | title: String!
125 | slug: String!
126 | tags: [String!]!
127 | snippet: String!
128 | content: String!
129 | published: Boolean!
130 | }
131 | ```
132 |
133 | 1. Deploy your backend to the cloud
134 |
135 | ```shell
136 | amplify push
137 | ```
138 |
139 |
140 |
141 | More details…
142 |
143 |
144 | ```console
145 | ? Do you want to generate code for your newly created GraphQL API Yes
146 | ? Choose the code generation language target javascript
147 | ? Enter the file name pattern of graphql queries, mutations and subscriptions src/graphql/**/*.js
148 | ? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions Yes
149 | ? Enter maximum statement depth [increase from default if your schema is deeply nested] 2
150 | ```
151 |
152 |
153 |
154 | 1. Run the app locally
155 |
156 | ```shell
157 | yarn dev
158 | ```
159 |
160 | Visit http://localhost:3000/ to view the app!
161 |
--------------------------------------------------------------------------------
/components/ContextMenu.js:
--------------------------------------------------------------------------------
1 | import { Menu, Transition } from "@headlessui/react";
2 | import { useUser } from "hooks/useUser";
3 |
4 | export function ContextMenu({ children = null }) {
5 | const user = useUser();
6 |
7 | if (!user) {
8 | return (
9 |
19 | );
20 | }
21 |
22 | return (
23 |
24 |
112 |
113 | );
114 | }
115 |
--------------------------------------------------------------------------------
/components/Footer.js:
--------------------------------------------------------------------------------
1 | export function Footer() {
2 | return (
3 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/components/Header.js:
--------------------------------------------------------------------------------
1 | import { Nav } from "components/Nav";
2 |
3 | export function Header({ children }) {
4 | return (
5 |
6 |
7 |
8 |
9 | {children}
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/components/Hero.js:
--------------------------------------------------------------------------------
1 | import { Header } from "components/Header";
2 |
3 | export function Hero({ children }) {
4 | return (
5 |
6 |
7 |
8 |
40 |
72 |
73 |
74 |
75 |
76 |
77 | );
78 | }
79 |
--------------------------------------------------------------------------------
/components/Nav.js:
--------------------------------------------------------------------------------
1 | export function Nav() {
2 | return (
3 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/components/Post.js:
--------------------------------------------------------------------------------
1 | import { Header } from "components/Header";
2 | import ReactMarkdown from "react-markdown";
3 |
4 | export function Post({ post }) {
5 | return (
6 | <>
7 |
8 |
9 |
41 |
73 |
105 |
106 |
107 |
108 |
128 |
129 |
130 |
131 |
132 |
139 |
140 |
141 |
{post.content}
142 |
143 |
144 | >
145 | );
146 | }
147 |
--------------------------------------------------------------------------------
/components/PostCard.js:
--------------------------------------------------------------------------------
1 | export function PostCard({ post }) {
2 | return (
3 |
7 |
8 | {!post.published && (
9 |
10 |
22 | Draft
23 |
24 | )}
25 |
26 |

31 |
32 |
33 |
55 |
56 |
57 |
64 | ·
65 |
66 | {Math.ceil(post.content.split(" ").length / 200)} min read
67 |
68 |
69 |
70 |
71 |
72 | );
73 | }
74 |
--------------------------------------------------------------------------------
/fixtures.js:
--------------------------------------------------------------------------------
1 | exports.posts = [
2 | {
3 | id: "abc1",
4 | content: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint harum rerum voluptatem quo recusandae magni placeat saepe molestiae, sed excepturi cumque corporis perferendis hic.`.repeat(
5 | 50
6 | ),
7 | createdAt: "2020-03-11T00:00:00.000Z",
8 | published: false,
9 | slug: "boost-your-conversion-rate",
10 | snippet: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint harum rerum voluptatem quo recusandae magni placeat saepe molestiae, sed excepturi cumque corporis perferendis hic.`,
11 | tags: ["blog"],
12 | title: "Placeholder for Boost your conversion rate",
13 | updatedAt: "2020-03-11T00:00:00.000Z",
14 | },
15 | {
16 | id: "abc2",
17 | content: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit facilis asperiores porro quaerat doloribus, eveniet dolore. Adipisci tempora aut inventore optio animi., tempore temporibus quo laudantium.`.repeat(
18 | 50
19 | ),
20 | createdAt: "2020-05-19T22:56:05.236Z",
21 | slug: "how-to-use-search-engine-optimization-to-drive-sales",
22 | snippet: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Velit facilis asperiores porro quaerat doloribus, eveniet dolore. Adipisci tempora aut inventore optio animi., tempore temporibus quo laudantium.`,
23 | published: true,
24 | tags: ["video"],
25 | title:
26 | "Placeholder for How to use search engine optimization to drive sales",
27 | updatedAt: "2020-05-19T22:56:05.236Z",
28 | },
29 | {
30 | id: "abc3",
31 | createdAt: "2020-10-24T22:56:05.236Z",
32 | content: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint harum rerum voluptatem quo recusandae magni placeat saepe molestiae, sed excepturi cumque corporis perferendis hic.`.repeat(
33 | 50
34 | ),
35 | published: true,
36 | slug: "improve-your-customer-experience",
37 | snippet: `Lorem ipsum dolor sit amet consectetur adipisicing elit. Sint harum rerum voluptatem quo recusandae magni placeat saepe molestiae, sed excepturi cumque corporis perferendis hic.`,
38 | tags: ["case study"],
39 | title: "Placeholder for Improve your customer experience",
40 | updatedAt: "2020-10-24T22:56:05.236Z",
41 | },
42 | ];
43 |
44 | exports.createPost = {
45 | data: {
46 | createPost: exports.posts[0],
47 | },
48 | };
49 |
50 | exports.updatePost = {
51 | data: {
52 | updatePost: exports.posts[0],
53 | },
54 | };
55 |
56 | exports.user = {
57 | username: "ericclemmons",
58 | attributes: {
59 | email: "eric@smarterspam.com",
60 | },
61 | };
62 |
--------------------------------------------------------------------------------
/hooks/useUser.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export function useUser() {
4 | const [user, setUser] = useState(null);
5 |
6 | useEffect(() => {
7 | // TODO Fetch `Auth.currentAuthenticatedUser`
8 | Promise.resolve(require("fixtures").user).then(setUser).catch(console.warn);
9 |
10 | // TODO Listen to `Hub` for `auth`'s { payload } for:
11 | // - `signIn` `event` (with `data`)
12 | // - `signOut` `event`
13 | }, []);
14 |
15 | return user;
16 | }
17 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "."
4 | }
5 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-tailwindcss",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "dev": "next",
6 | "build": "next build",
7 | "start": "next start"
8 | },
9 | "dependencies": {
10 | "@headlessui/react": "^0.2.0",
11 | "@tailwindcss/ui": "^0.6.2",
12 | "lodash": "^4.17.20",
13 | "next": "latest",
14 | "react": "^16.13.1",
15 | "react-dom": "^16.13.1",
16 | "react-markdown": "^4.3.1"
17 | },
18 | "devDependencies": {
19 | "postcss-flexbugs-fixes": "4.2.1",
20 | "postcss-preset-env": "^6.7.0",
21 | "tailwindcss": "^1.9.4"
22 | },
23 | "license": "MIT"
24 | }
25 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import { Footer } from "components/Footer";
2 | import Head from "next/head";
3 | import "../styles/index.css";
4 |
5 | function MyApp({ Component, pageProps }) {
6 | return (
7 | <>
8 |
9 | Next.js + Ampify Blog
10 |
11 |
12 |
13 |
14 |
15 | >
16 | );
17 | }
18 |
19 | export default MyApp;
20 |
--------------------------------------------------------------------------------
/pages/api/preview.js:
--------------------------------------------------------------------------------
1 | export default async function preview(req, res) {
2 | try {
3 | console.error("TODO Check session for `Auth.currentAuthenticatedUser()`");
4 | } catch (error) {
5 | return res.status(401).json(error);
6 | }
7 |
8 | res.setPreviewData({});
9 |
10 | const { slug } = req.query;
11 |
12 | if (!slug) {
13 | return res.redirect(302, "/");
14 | }
15 |
16 | // TODO Fetch posts by `slug` and get first `post`
17 | const [post] = require("fixtures").posts.filter((post) => post.slug === slug);
18 |
19 | if (!post) {
20 | return res.status(404).json({ message: "Post not found" });
21 | }
22 |
23 | res.redirect(`/posts/${post.slug}`);
24 | }
25 |
--------------------------------------------------------------------------------
/pages/edit/[slug].js:
--------------------------------------------------------------------------------
1 | import { Menu } from "@headlessui/react";
2 | import { ContextMenu } from "components/ContextMenu";
3 | import { Header } from "components/Header";
4 | import { kebabCase } from "lodash";
5 | import { useRef, useState } from "react";
6 |
7 | export async function getServerSideProps({ params, req, res }) {
8 | const { slug } = params;
9 |
10 | try {
11 | console.warn("TODO Check session for `Auth.currentAuthenticatedUser()`");
12 | } catch (error) {
13 | res.statusCode = 302;
14 | res.setHeader("location", "/");
15 | res.end();
16 | }
17 |
18 | // TODO Fetch posts by `slug` and get first `post`
19 | const [post] = require("fixtures").posts.filter((post) => post.slug === slug);
20 |
21 | if (!post) {
22 | res.statusCode = 302;
23 | res.setHeader("location", "/edit/new");
24 | res.end();
25 | }
26 |
27 | return {
28 | props: { post },
29 | };
30 | }
31 |
32 | export default function EditPost({ post }) {
33 | const formRef = useRef(null);
34 | const [updatedPost, setUpdatedPost] = useState(null);
35 |
36 | function handleChange(event) {
37 | const formData = new FormData(formRef.current);
38 |
39 | // Convert string values into `Post` structure
40 | setUpdatedPost({
41 | published: Boolean(formData.get("published")),
42 | title: String(formData.get("title")).trim(),
43 | slug: kebabCase(String(formData.get("title"))),
44 | tags: String(formData.get("tags"))
45 | .split(",")
46 | .map((tag) => tag.trim())
47 | .filter(Boolean),
48 | snippet: String(formData.get("snippet")).trim(),
49 | content: String(formData.get("content")).trim(),
50 | });
51 | }
52 |
53 | function handleDelete() {
54 | if (confirm("Are you sure?")) {
55 | // TODO Delete post by `post.id`
56 | Promise.resolve()
57 | .then(() => {
58 | window.location.href = "/";
59 | })
60 | .catch(console.warn);
61 | }
62 | }
63 |
64 | function handleSubmit(event) {
65 | event.preventDefault();
66 |
67 | if (post.id) {
68 | // TODO Mutate post with `updatedPost` as `input`
69 | Promise.resolve(require("fixtures").updatePost)
70 | .then((response) => {
71 | window.location.href = `/api/preview?slug=${response.data.updatePost.slug}`;
72 | })
73 | .catch(console.error);
74 | } else {
75 | // TODO Create post with `updatedPost` as `input`
76 | Promise.resolve(require("fixtures").createPost)
77 | .then((response) => {
78 | window.location.href = `/api/preview?slug=${response.data.createPost.slug}`;
79 | })
80 | .catch(console.error);
81 | }
82 | }
83 |
84 | return (
85 | <>
86 |
93 |
94 |
95 | {post.id && (
96 | <>
97 |
98 | {({ active }) => (
99 |
105 |
118 | Preview Draft
119 |
120 | )}
121 |
122 |
123 | {({ active }) => (
124 |
144 | )}
145 |
146 | >
147 | )}
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 | The header image will automatically be chosen for you from{" "}
156 |
157 | unsplash
158 | {" "}
159 | based on your tags
.
160 |
161 |
162 |
163 |
164 | JSON
165 |
166 |
167 | Preview of what JSON will be sent to the API:
168 |
169 |
170 |
171 |
180 |
181 |
182 |
183 |
184 |
334 |
335 |
336 | >
337 | );
338 | }
339 |
--------------------------------------------------------------------------------
/pages/edit/new.js:
--------------------------------------------------------------------------------
1 | import EditPost from "./[slug]";
2 |
3 | export async function getServerSideProps({ params, req, res }) {
4 | try {
5 | console.error("TODO Check session for `Auth.currentAuthenticatedUser()`");
6 | } catch (error) {
7 | res.statusCode = 302;
8 | res.setHeader("location", "/");
9 | res.end();
10 | }
11 |
12 | return {
13 | props: {
14 | post: {
15 | published: false,
16 | },
17 | },
18 | };
19 | }
20 |
21 | export default function NewPost({ post }) {
22 | return ;
23 | }
24 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import { ContextMenu } from "components/ContextMenu";
2 | import { Hero } from "components/Hero";
3 | import { PostCard } from "components/PostCard";
4 |
5 | export async function getStaticProps({ preview, req }) {
6 | // TODO List posts with `filter` where `published` equals `true`
7 | return {
8 | props: {
9 | posts: require("fixtures").posts.filter(
10 | (post) => preview || post.published
11 | ),
12 | },
13 | };
14 | }
15 |
16 | export default function IndexPage({ posts = [] }) {
17 | return (
18 | <>
19 |
20 |
21 |
22 |
27 |
28 |
29 | Welcome to my
30 | Next.js + Amplify Blog
31 |
32 |
33 | Powered by Amazon Cognito for Authentication and{" "}
34 | AWS AppSync for data.
35 |
36 |
66 |
67 |
68 |
69 |
72 |
73 |
74 |
75 | From the blog
76 |
77 |
78 | Trying to share what I've learned, as time allows.
79 |
80 |
81 |
82 | {posts.map((post) => (
83 |
84 | ))}
85 |
86 |
87 |
88 | >
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/pages/login.js:
--------------------------------------------------------------------------------
1 | import { Hero } from "components/Hero";
2 | import { useUser } from "hooks/useUser";
3 | import { useEffect } from "react";
4 |
5 | export default function Login() {
6 | const user = useUser();
7 |
8 | useEffect(() => {
9 | if (user) window.location.href = "/api/preview";
10 | }, [user]);
11 |
12 | return (
13 |
14 | {/* TODO Replace with AmplifyAuthenticator */}
15 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/pages/posts/[slug].js:
--------------------------------------------------------------------------------
1 | import { Menu } from "@headlessui/react";
2 | import { ContextMenu } from "components/ContextMenu";
3 | import { Hero } from "components/Hero";
4 | import { Post } from "components/Post";
5 | import Error from "next/error";
6 | import { useRouter } from "next/router";
7 |
8 | export async function getStaticPaths() {
9 | // TODO List posts with `filter` where `published` equals `true`
10 | const posts = [];
11 |
12 | const paths = posts.map(({ slug }) => ({
13 | params: { slug },
14 | }));
15 |
16 | return {
17 | fallback: true,
18 | paths,
19 | };
20 | }
21 |
22 | export async function getStaticProps({ params, preview = false }) {
23 | const { slug } = params;
24 |
25 | // TODO Fetch posts by `slug` and get first `post`.
26 | // (Filter by `published` if not `preview`)
27 | const posts = require("fixtures").posts.filter((post) => post.slug === slug);
28 | const [post = null] = posts;
29 |
30 | return {
31 | props: { post },
32 | };
33 | }
34 |
35 | export default function PostPage({ post }) {
36 | const router = useRouter();
37 |
38 | function publishDraft() {
39 | // TODO Update post with `published: true` where `id` matches `post.id`
40 | // (Needs "AMAZON_COGNITO_USER_POOLS")
41 | Promise.resolve(require("fixtures").updatePost)
42 | .then((response) => {
43 | window.location.href = `/posts/${response.data.updatePost.slug}`;
44 | })
45 | .catch(console.warn);
46 | }
47 |
48 | function convertToDraft() {
49 | // TODO Update post with `published: false` where `id` matches `post.id`
50 | // (Needs "AMAZON_COGNITO_USER_POOLS")
51 | Promise.resolve(require("fixtures").updatePost)
52 | .then((response) => {
53 | window.location.href = `/edit/${response.data.updatePost.slug}`;
54 | })
55 | .catch(console.warn);
56 | }
57 |
58 | if (router.isFallback) {
59 | return (
60 |
61 |
62 | …
63 |
64 |
65 | );
66 | }
67 |
68 | if (!post) {
69 | return ;
70 | }
71 |
72 | return (
73 | <>
74 | {!post.published && (
75 |
76 |
77 |
78 |
79 |
80 |
92 |
93 |
94 | This is a draft post.
95 |
96 |
97 |
98 |
99 |
100 | )}
101 |
102 |
174 | >
175 | );
176 | }
177 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | 'tailwindcss',
4 | 'postcss-flexbugs-fixes',
5 | [
6 | 'postcss-preset-env',
7 | {
8 | autoprefixer: {
9 | flexbox: 'no-2009',
10 | },
11 | stage: 3,
12 | features: {
13 | 'custom-properties': false,
14 | },
15 | },
16 | ],
17 | ],
18 | }
19 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ericclemmons/next.js-conf-2020/5766476b827708afebb43e7b5540e86f89eacfdc/screenshot.png
--------------------------------------------------------------------------------
/slides.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ericclemmons/next.js-conf-2020/5766476b827708afebb43e7b5540e86f89eacfdc/slides.key
--------------------------------------------------------------------------------
/styles/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | /* Write your own custom base styles here */
4 |
5 | /* Start purging... */
6 | @tailwind components;
7 | /* Stop purging. */
8 |
9 | /* Write your own custom component styles here */
10 | .btn-blue {
11 | @apply px-4 py-2 font-bold text-white bg-blue-500 rounded;
12 | }
13 |
14 | /* Start purging... */
15 | @tailwind utilities;
16 | /* Stop purging. */
17 |
18 | /* Your own custom utilities */
19 |
20 | /* Make fit nicely in a fly-out panel */
21 | amplify-authenticator {
22 | --container-height: auto;
23 | --container-justify: center;
24 | --margin-bottom: 0;
25 | --min-width: 100%;
26 | --width: 100%;
27 | }
28 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | future: {
3 | removeDeprecatedGapUtilities: true,
4 | purgeLayersByDefault: true,
5 | },
6 | purge: ["./components/**/*.{js,ts,jsx,tsx}", "./pages/**/*.{js,ts,jsx,tsx}"],
7 | theme: {
8 | extend: {
9 | colors: {
10 | "accent-1": "#333",
11 | },
12 | },
13 | },
14 | variants: {},
15 | plugins: [require("@tailwindcss/ui")],
16 | };
17 |
--------------------------------------------------------------------------------