├── .eslintrc.json ├── .gitignore ├── README.md ├── components ├── InputField.tsx ├── SelectField.tsx └── TermsOfService.tsx ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── api │ └── hello.ts ├── index.tsx └── success.tsx ├── postcss.config.js ├── public ├── Lato-Bold.ttf ├── Lato-Regular.ttf ├── favicon.ico └── form.png ├── styles ├── fonts.css └── globals.css ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚙️ Project 2 | 3 | Form using React.js and Tailwind CSS, with the implementation of a responsive design and CSS interactions with the fields. 4 | 5 | # ✈️ Technologies 6 | 7 | - Next.js 8 | - TypeScript 9 | - Tailwind CSS 10 | - Formik 11 | - Yup 12 | - Framer Motion 13 | - React Confetti 14 | -------------------------------------------------------------------------------- /components/InputField.tsx: -------------------------------------------------------------------------------- 1 | interface InputFieldProps { 2 | id: string; 3 | label: string; 4 | placeholder: string; 5 | type?: string; 6 | value: string; 7 | onChange: { 8 | (e: React.ChangeEvent): void; 9 | >( 10 | field: T_1 11 | ): T_1 extends React.ChangeEvent 12 | ? void 13 | : (e: string | React.ChangeEvent) => void; 14 | }; 15 | onBlur: { 16 | (e: React.FocusEvent): void; 17 | (fieldOrEvent: T): T extends string ? (e: any) => void : void; 18 | }; 19 | errorMessage?: string; 20 | touched: boolean | undefined; 21 | } 22 | 23 | export default function InputField({ 24 | id, 25 | label, 26 | placeholder, 27 | type = "text", 28 | value, 29 | onChange, 30 | onBlur, 31 | errorMessage, 32 | touched, 33 | }: InputFieldProps) { 34 | const error = errorMessage && touched; 35 | 36 | return ( 37 |
38 | 46 | 60 |
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /components/SelectField.tsx: -------------------------------------------------------------------------------- 1 | interface SelectFieldProps { 2 | id: string; 3 | label: string; 4 | options: string[] | number[]; 5 | value: string; 6 | onChange: any; 7 | } 8 | 9 | export default function SelectField({ 10 | id, 11 | label, 12 | options, 13 | value, 14 | onChange, 15 | }: SelectFieldProps) { 16 | return ( 17 |
18 | 21 | 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /components/TermsOfService.tsx: -------------------------------------------------------------------------------- 1 | interface TermsOfServiceProps { 2 | onChange: { 3 | (e: React.ChangeEvent): void; 4 | >( 5 | field: T_1 6 | ): T_1 extends React.ChangeEvent 7 | ? void 8 | : (e: string | React.ChangeEvent) => void; 9 | }; 10 | errorMessage: string | string[] | never[] | undefined; 11 | touched: never[] | undefined; 12 | } 13 | 14 | export default function TermsOfService({ 15 | onChange, 16 | errorMessage, 17 | touched, 18 | }: TermsOfServiceProps) { 19 | const error = errorMessage && touched; 20 | 21 | return ( 22 |
23 | 31 |
32 | 44 |

45 | I agree to the Terms and Service that my data will be taken and sold. 46 |

47 |
48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | } 6 | 7 | module.exports = nextConfig 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-form", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/node": "18.11.13", 13 | "@types/react": "18.0.26", 14 | "@types/react-dom": "18.0.9", 15 | "eslint": "8.29.0", 16 | "eslint-config-next": "13.0.6", 17 | "formik": "^2.2.9", 18 | "framer-motion": "^7.7.2", 19 | "next": "13.0.6", 20 | "react": "18.2.0", 21 | "react-confetti": "^6.1.0", 22 | "react-dom": "18.2.0", 23 | "typescript": "4.9.4", 24 | "yup": "^0.32.11" 25 | }, 26 | "devDependencies": { 27 | "@tailwindcss/forms": "^0.5.3", 28 | "autoprefixer": "^10.4.13", 29 | "postcss": "^8.4.20", 30 | "tailwindcss": "^3.2.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | import type { AppProps } from "next/app"; 3 | import { AnimatePresence } from "framer-motion"; 4 | 5 | export default function App({ Component, pageProps, router }: AppProps) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from "next"; 3 | 4 | type Data = { 5 | name: string; 6 | }; 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: "Whitehorse" }); 13 | } 14 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Image from "next/image"; 3 | import { useFormik } from "formik"; 4 | import { useRouter } from "next/router"; 5 | import * as Yup from "yup"; 6 | import { motion } from "framer-motion"; 7 | 8 | import formImage from "../public/form.png"; 9 | import InputField from "../components/InputField"; 10 | import SelectField from "../components/SelectField"; 11 | import TermsOfService from "../components/TermsOfService"; 12 | 13 | const siteTitle = "Next.js Form"; 14 | 15 | export default function Home() { 16 | const countries = [ 17 | "United States", 18 | "Canada", 19 | "United Kingdom", 20 | "France", 21 | "Germany", 22 | "Norway", 23 | ]; 24 | 25 | // Next.js Router 26 | const router = useRouter(); 27 | 28 | // Formic Logics 29 | const formik = useFormik({ 30 | initialValues: { 31 | name: "", 32 | email: "", 33 | country: countries[0], 34 | terms: [], 35 | }, 36 | 37 | validationSchema: Yup.object({ 38 | name: Yup.string() 39 | .min(2, "Name must be 2 characters or more.") 40 | .max(20, "Name must be 20 characters or less.") 41 | .required("Name is required"), 42 | email: Yup.string() 43 | .email("Invalid email address.") 44 | .required("Email is required"), 45 | terms: Yup.array() 46 | .length(1, "Terms of service must be checked.") 47 | .required("Terms of service must be checked."), 48 | }), 49 | 50 | onSubmit: (values) => { 51 | console.log(values); 52 | router.push({ 53 | pathname: "/success", 54 | query: values, 55 | }); 56 | }, 57 | }); 58 | 59 | return ( 60 | 65 | 66 | {siteTitle} 67 | 68 | 69 | 75 | 76 | 77 | 78 | 79 |
80 |
84 |
85 |

86 | Let's get started 👋 87 |

88 |

89 | Join our E-learning platform today and unlock over 500+ courses 90 | and digital assets ready to download. 91 |

92 | 93 |
94 | {/* Name Input Field */} 95 | 105 | {/* Email Input Field */} 106 | 117 | 124 | 129 |
130 | 131 | 144 |
145 | 146 |
147 | form-learn 155 |
156 |
157 |
158 |
159 | ); 160 | } 161 | -------------------------------------------------------------------------------- /pages/success.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useRouter } from "next/router"; 3 | import { motion } from "framer-motion"; 4 | import Confetti from "react-confetti"; 5 | 6 | export default function Success() { 7 | const router = useRouter(); 8 | 9 | const [pieces, setPieces] = useState(200); 10 | const stopConfetti = () => { 11 | setTimeout(() => setPieces(0), 3000); 12 | }; 13 | 14 | useEffect(() => stopConfetti(), []); 15 | 16 | return ( 17 | 23 |
24 |

25 | Thanks for the Email {router.query.name} ✨ 26 |

27 |

28 | We have sent you an email over at{" "} 29 | {router.query.email}. We will 30 | get back to you as soon we can! 31 |

32 |
33 | 34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whitehorse21/nextjs-form/55de8c9754d732a3b4ea87e37812a95a32c08548/public/Lato-Bold.ttf -------------------------------------------------------------------------------- /public/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whitehorse21/nextjs-form/55de8c9754d732a3b4ea87e37812a95a32c08548/public/Lato-Regular.ttf -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whitehorse21/nextjs-form/55de8c9754d732a3b4ea87e37812a95a32c08548/public/favicon.ico -------------------------------------------------------------------------------- /public/form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whitehorse21/nextjs-form/55de8c9754d732a3b4ea87e37812a95a32c08548/public/form.png -------------------------------------------------------------------------------- /styles/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Lato Regular"; 3 | src: url("../public/Lato-Regular.ttf"); 4 | } 5 | 6 | @font-face { 7 | font-family: "Lato Bold"; 8 | src: url("../public/Lato-Bold.ttf"); 9 | } 10 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | padding: 0; 8 | margin: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 10 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 11 | background-color: rgb(20, 184, 166); 12 | } 13 | 14 | a { 15 | color: inherit; 16 | text-decoration: none; 17 | } 18 | 19 | * { 20 | box-sizing: border-box; 21 | } 22 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | fontFamily: { 10 | latoRegular: ["Lato Regular", "sans-serif"], 11 | latoBold: ["Lato Bold", "sans-serif"], 12 | }, 13 | screens: { 14 | laptop: { max: "1400px" }, 15 | tablet: { max: "980px" }, 16 | mobile: { max: "640px" }, 17 | }, 18 | }, 19 | }, 20 | plugins: [require("@tailwindcss/forms")], 21 | }; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | --------------------------------------------------------------------------------