├── .gitignore
├── README.md
├── components
├── Toast.js
└── ToastContainer.js
├── context
└── ToastContext.js
├── hooks
└── useToast.js
├── package-lock.json
├── package.json
├── pages
├── _app.js
├── api
│ └── contact.js
└── index.js
├── postcss.config.js
├── public
├── favicon.ico
└── vercel.svg
├── styles
├── Home.module.css
└── globals.css
└── tailwind.config.js
/.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 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/components/Toast.js:
--------------------------------------------------------------------------------
1 | import { useToastDispatchContext } from '../context/ToastContext';
2 |
3 | export default function Toast({ type, message, id }) {
4 | const dispatch = useToastDispatchContext();
5 | return (
6 | <>
7 | {type == 'success' && (
8 |
9 |
10 |
24 |
27 |
28 |
29 |
49 |
50 |
51 |
52 |
53 | )}
54 | {type == 'error' && (
55 |
56 |
57 |
71 |
74 |
75 |
76 |
96 |
97 |
98 |
99 |
100 | )}
101 | {type == 'update' && (
102 |
103 |
104 |
118 |
121 |
122 |
123 |
143 |
144 |
145 |
146 |
147 | )}
148 | {type == 'warning' && (
149 |
150 |
151 |
165 |
168 |
169 |
170 |
190 |
191 |
192 |
193 |
194 | )}
195 | >
196 | );
197 | }
198 |
--------------------------------------------------------------------------------
/components/ToastContainer.js:
--------------------------------------------------------------------------------
1 | import Toast from '../components/Toast';
2 | import { useToastStateContext } from '../context/ToastContext';
3 |
4 | export default function ToastContainer() {
5 | const { toasts } = useToastStateContext();
6 | return (
7 |
8 |
9 | {toasts &&
10 | toasts.map((toast) => (
11 |
17 | ))}
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/context/ToastContext.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useReducer } from 'react';
2 |
3 | const ToastStateContext = createContext({ toasts: [] });
4 | const ToastDispatchContext = createContext(null);
5 |
6 | function ToastReducer(state, action) {
7 | switch (action.type) {
8 | case 'ADD_TOAST': {
9 | return {
10 | ...state,
11 | toasts: [...state.toasts, action.toast],
12 | };
13 | }
14 | case 'DELETE_TOAST': {
15 | const updatedToasts = state.toasts.filter((e) => e.id != action.id);
16 | return {
17 | ...state,
18 | toasts: updatedToasts,
19 | };
20 | }
21 | default: {
22 | throw new Error('unhandled action type');
23 | }
24 | }
25 | }
26 |
27 | export function ToastProvider({ children }) {
28 | const [state, dispatch] = useReducer(ToastReducer, {
29 | toasts: [],
30 | });
31 | return (
32 |
33 |
34 | {children}
35 |
36 |
37 | );
38 | }
39 |
40 | export const useToastDispatchContext = () => useContext(ToastDispatchContext);
41 | export const useToastStateContext = () => useContext(ToastStateContext);
42 |
--------------------------------------------------------------------------------
/hooks/useToast.js:
--------------------------------------------------------------------------------
1 | import { useToastDispatchContext } from '../context/ToastContext';
2 |
3 | export function useToast() {
4 | const dispatch = useToastDispatchContext();
5 |
6 | function toast(type, message) {
7 | const id = Math.random().toString(36).substr(2, 9);
8 | dispatch({ type: 'ADD_TOAST', toast: { type, message, id } });
9 |
10 | setTimeout(() => {
11 | dispatch({ type: 'DELETE_TOAST', id: id });
12 | }, 4000);
13 | }
14 |
15 | return toast;
16 | }
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "forms",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "axios": "^0.21.1",
12 | "next": "10.0.7",
13 | "nodemailer": "^6.4.18",
14 | "react": "17.0.1",
15 | "react-dom": "17.0.1",
16 | "react-hook-form": "^6.15.3"
17 | },
18 | "devDependencies": {
19 | "autoprefixer": "^10.2.4",
20 | "postcss": "^8.2.6",
21 | "tailwindcss": "^2.0.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css';
2 | import ToastContainer from '../components/ToastContainer';
3 | import { ToastProvider } from '../context/ToastContext';
4 | function MyApp({ Component, pageProps }) {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | export default MyApp;
16 |
--------------------------------------------------------------------------------
/pages/api/contact.js:
--------------------------------------------------------------------------------
1 | import nodemailer from 'nodemailer';
2 | export default async (req, res) => {
3 | const { name, email, message, phone } = req.body;
4 |
5 | const transporter = nodemailer.createTransport({
6 | host: 'smtp.gmail.com',
7 | port: 465,
8 | secure: true,
9 | auth: {
10 | user: process.env.user,
11 | pass: process.env.pass,
12 | },
13 | });
14 |
15 | try {
16 | // const emailRes = await transporter.sendMail({
17 | // from: email,
18 | // to: 'adaamr@gmail.com',
19 | // subject: `Contact form submission from ${name}`,
20 | // html: `You have a new contact form submission
21 | // Name: ${name}
22 | // Phone: ${phone}
23 | // Message: ${message}
24 |
25 | // `,
26 | // });
27 |
28 | console.log('Message Sent');
29 | } catch (err) {
30 | console.log(err);
31 | }
32 |
33 | res.status(200).json(req.body);
34 | };
35 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import { useForm } from 'react-hook-form';
2 | import axios from 'axios';
3 | import { useRouter } from 'next/router';
4 | import { useToast } from '../hooks/useToast';
5 |
6 | export default function Home() {
7 | const toast = useToast();
8 | const { register, handleSubmit, errors, reset } = useForm();
9 | const router = useRouter();
10 | async function onSubmitForm(values) {
11 | let config = {
12 | method: 'post',
13 | url: `${process.env.NEXT_PUBLIC_API_URL}/api/contact`,
14 | headers: {
15 | 'Content-Type': 'application/json',
16 | },
17 | data: values,
18 | };
19 |
20 | try {
21 | const response = await axios(config);
22 | console.log(response);
23 | if (response.status == 200) {
24 | reset();
25 | toast(
26 | 'success',
27 | 'Thank you for contacting us, we will be in touch soon.'
28 | );
29 | }
30 | } catch (err) {}
31 | }
32 |
33 | return (
34 |
146 | );
147 | }
148 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adamrichardson14/forms/7f259f5cf0d416d11292a25997eaa23194a6ce2b/public/favicon.ico
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .main {
11 | padding: 5rem 0;
12 | flex: 1;
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | align-items: center;
17 | }
18 |
19 | .footer {
20 | width: 100%;
21 | height: 100px;
22 | border-top: 1px solid #eaeaea;
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | }
27 |
28 | .footer img {
29 | margin-left: 0.5rem;
30 | }
31 |
32 | .footer a {
33 | display: flex;
34 | justify-content: center;
35 | align-items: center;
36 | }
37 |
38 | .title a {
39 | color: #0070f3;
40 | text-decoration: none;
41 | }
42 |
43 | .title a:hover,
44 | .title a:focus,
45 | .title a:active {
46 | text-decoration: underline;
47 | }
48 |
49 | .title {
50 | margin: 0;
51 | line-height: 1.15;
52 | font-size: 4rem;
53 | }
54 |
55 | .title,
56 | .description {
57 | text-align: center;
58 | }
59 |
60 | .description {
61 | line-height: 1.5;
62 | font-size: 1.5rem;
63 | }
64 |
65 | .code {
66 | background: #fafafa;
67 | border-radius: 5px;
68 | padding: 0.75rem;
69 | font-size: 1.1rem;
70 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
71 | Bitstream Vera Sans Mono, Courier New, monospace;
72 | }
73 |
74 | .grid {
75 | display: flex;
76 | align-items: center;
77 | justify-content: center;
78 | flex-wrap: wrap;
79 | max-width: 800px;
80 | margin-top: 3rem;
81 | }
82 |
83 | .card {
84 | margin: 1rem;
85 | flex-basis: 45%;
86 | padding: 1.5rem;
87 | text-align: left;
88 | color: inherit;
89 | text-decoration: none;
90 | border: 1px solid #eaeaea;
91 | border-radius: 10px;
92 | transition: color 0.15s ease, border-color 0.15s ease;
93 | }
94 |
95 | .card:hover,
96 | .card:focus,
97 | .card:active {
98 | color: #0070f3;
99 | border-color: #0070f3;
100 | }
101 |
102 | .card h3 {
103 | margin: 0 0 1rem 0;
104 | font-size: 1.5rem;
105 | }
106 |
107 | .card p {
108 | margin: 0;
109 | font-size: 1.25rem;
110 | line-height: 1.5;
111 | }
112 |
113 | .logo {
114 | height: 1em;
115 | }
116 |
117 | @media (max-width: 600px) {
118 | .grid {
119 | width: 100%;
120 | flex-direction: column;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: [],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {},
6 | },
7 | variants: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
--------------------------------------------------------------------------------