diff --git a/README.md b/README.md index 31ad3fb..baf0774 100644 --- a/README.md +++ b/README.md @@ -1,322 +1,66 @@ - +# Document Upload App with Next.js -# Next.js Enterprise Boilerplate
[![GitHub Actions Workflow Status][check-workflow-badge]][check-workflow-badge-link] [![GitHub License][github-license-badge]][github-license-badge-link] [![GitHub contributors][github-contributors-badge]][github-contributors-badge-link] [![Discord][discord-badge]][discord-badge-link] [![Blazity][made-by-blazity-badge]][made-by-blazity-badge-link] - -Welcome to the _Next.js Enterprise Boilerplate_, an open-source template for enterprise projects! It's loaded with features that'll help you build a high-performance, maintainable, and enjoyable app. We've done all the heavy lifting for you, so sit back, relax, and get ready to conquer the world with your incredible app! ๐ŸŒ - -> [!NOTE] -> **Blazity** is a group of Next.js/Headless experts. Contact us at [contact@blazity.com](https://blazity.com) if youโ€™d like to talk about your project or just to have a chat with us +This project is built on the robust foundation of the Next.js Enterprise Boilerplate by Blazity, tailored for a document upload application. It demonstrates advanced usage of Next.js, Formik for form management, Tailwind CSS for styling, and includes comprehensive testing with Jest and Playwright. This README guides you through the project setup, features, and deployment. ## Features -With this template, you get all the awesomeness you need: +- **Next.js**: Leveraging the latest features of Next.js for SSR and static generation. +- **Tailwind CSS**: Utilizing Tailwind for rapid and responsive UI development. +- **Formik**: Streamlined form handling with validation and custom input components. +- **Playwright**: End-to-end testing to ensure reliability and functionality. +- **Radix UI**: Utilizing Radix UI for accessible and customizable components. +- **Tailwind Merge**: Efficiently managing Tailwind CSS classes. +- **TypeScript**: Ensuring type safety and developer productivity. -- ๐ŸŽ๏ธ **[Next.js](https://nextjs.org/)** - Fast by default, with config optimized for performance (with **App Directory**) -- ๐Ÿ’… **[Tailwind CSS](https://tailwindcss.com/)** - A utility-first CSS framework for rapid UI development -- โœจ **[ESlint](https://eslint.org/)** and **[Prettier](https://prettier.io/)** - For clean, consistent, and error-free code -- ๐Ÿ› ๏ธ **[Extremely strict TypeScript](https://www.typescriptlang.org/)** - With [`ts-reset`](https://github.com/total-typescript/ts-reset) library for ultimate type safety -- ๐Ÿš€ **[GitHub Actions](https://github.com/features/actions)** - Pre-configured actions for smooth workflows, including Bundle Size and performance stats -- ๐Ÿ’ฏ **Perfect Lighthouse score** - Because performance matters -- **[Bundle analyzer plugin](https://www.npmjs.com/package/@next/bundle-analyzer)** - Keep an eye on your bundle size -- **[Jest](https://jestjs.io/)** and **[React Testing Library](https://testing-library.com/react)** - For rock-solid unit and integration tests -- **[Playwright](https://playwright.dev/)** - Write end-to-end tests like a pro -- **[Storybook](https://storybook.js.org/)** - Create, test, and showcase your components -- **Smoke Testing** and **Acceptance Tests** - For confidence in your deployments -- **[Conventional commits git hook](https://www.conventionalcommits.org/)** - Keep your commit history neat and tidy -- **[Observability](https://opentelemetry.io/)** - Open Telemetry integration for seamless monitoring -- **[Absolute imports](https://nextjs.org/docs/advanced-features/module-path-aliases)** - No more spaghetti imports -- **[Health checks](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/)** - Kubernetes-compatible for robust deployments -- **[Radix UI](https://www.radix-ui.com/)** - Headless UI components for endless customization -- **[CVA](http://cva.style/)** - Create a consistent, reusable, and atomic design system -- **[Renovate BOT](https://www.whitesourcesoftware.com/free-developer-tools/renovate)** - Auto-updating dependencies, so you can focus on coding -- **[Patch-package](https://www.npmjs.com/package/patch-package)** - Fix external dependencies without losing your mind -- **Components coupling and cohesion graph** - A tool for managing component relationships -- **[Automated ChatGPT Code Reviews](https://openai.com/chatgpt)** - **Stay on the cutting edge with AI-powered code reviews!** -- **[Semantic Release](https://github.com/semantic-release/semantic-release)** - for automatic changelog -- **[T3 Env](https://env.t3.gg/)** - Manage your environment variables with ease +## Getting Started -## Table of Contents +To run this project locally, follow these steps: -- [Next.js Enterprise Boilerplate](#nextjs-enterprise-boilerplate) - - [Features](#features) - - [Table of Contents](#table-of-contents) - - [Getting Started](#-getting-started) - - [Deployment](#-deployment) - - [Scripts Overview](#-scripts-overview) - - [Coupling Graph](#-coupling-graph) - - [Testing](#-testing) - - [Running Tests](#running-tests) - - [Acceptance Tests](#acceptance-tests) - - [Smoke Testing](#smoke-testing) - - [Styling and Design System](#-styling-and-design-system) - - [CVA - A New Approach to Variants](#cva---a-new-approach-to-variants) - - [State Management](#-state-management) - - [Zustand](#zustand) - - [Jotai](#jotai) - - [Recoil](#recoil) - - [ChatGPT Code Review](#-chatgpt-code-review) - - [Environment Variables handling](#-environment-variables-handling) - - [Contribution](#-contribution) - - [Support](#support) - - [License](#-license) - - [Contributors](#contributors) - -## ๐ŸŽฏ Getting Started - -To get started with this boilerplate, follow these steps: - -1. Fork & clone repository: +1. Clone the repository: ```bash -## Don't forget to โญ star and fork it first :) -git clone https://github.com/ - -### Acceptance Tests - -To write acceptance tests, we leverage Storybook's [`play` function](https://storybook.js.org/docs/react/writing-stories/play-function#writing-stories-with-the-play-function). This allows you to interact with your components and test various user flows within Storybook. - -```ts -/* - * See https://storybook.js.org/docs/react/writing-stories/play-function#working-with-the-canvas - * to learn more about using the canvasElement to query the DOM - */ -export const FilledForm: Story = { - play: async ({ canvasElement }) => { - const canvas = within(canvasElement) - - const emailInput = canvas.getByLabelText("email", { - selector: "input", - }) - - await userEvent.type(emailInput, "example-email@email.com", { - delay: 100, - }) - - const passwordInput = canvas.getByLabelText("password", { - selector: "input", - }) - - await userEvent.type(passwordInput, "ExamplePassword", { - delay: 100, - }) - // See https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args to learn how to setup logging in the Actions panel - const submitButton = canvas.getByRole("button") - - await userEvent.click(submitButton) - }, -} -``` - -### Smoke Testing - -In this boilerplate, we use Storybook's out-of-the-box support for smoke testing to verify that components render correctly without any errors. Just run `yarn test-storybook` to perform smoke testing. Remember to write stories in JSX or TSX format only. Smoke testing and a lot of other functionalities dont work well with MDX stories. - -## ๐ŸŽจ Styling and Design System - -This boilerplate uses Tailwind CSS for styling and CVA for creating a powerful, easy-to-use design system. If you want to learn more about the setup, check out this fantastic video by Vercel: - -[![Styling and Design System](https://img.youtube.com/vi/T-Zv73yZ_QI/0.jpg)](https://www.youtube.com/watch?v=T-Zv73yZ_QI&ab_channel=Vercel) - -### CVA - A New Approach to Variants - -While CSS-in-TS libraries such as [Stitches](https://stitches.dev/) and [Vanilla Extract](https://vanilla-extract.style/) are great for building type-safe UI components, they might not be the perfect fit for everyone. You may prefer more control over your stylesheets, need to use a framework like Tailwind CSS, or simply enjoy writing your own CSS. - -Creating variants using traditional CSS can be a tedious task, requiring you to manually match classes to props and add types. CVA is here to take that pain away, allowing you to focus on the enjoyable aspects of UI development. By providing an easy and type-safe way to create variants, CVA simplifies the process and helps you create powerful design systems without compromising on the flexibility and control of CSS. - -## ๐Ÿ’พ State Management - -While this boilerplate doesn't include a specific state management library, we believe it's essential for you to choose the one that best suits your project's needs. Here are some libraries we recommend for state management: - -### Zustand - -[Zustand](https://github.com/pmndrs/zustand) is a small, fast, and scalable state management library. It's designed to be simple and intuitive, making it a great choice for small to medium-sized projects. It's also optimized for bundle size, ensuring minimal impact on your app's performance. - -### Jotai - -[Jotai](https://github.com/pmndrs/jotai) is an atom-based state management library for React that focuses on providing a minimal and straightforward API. Its atom-based approach allows you to manage your state in a granular way while still being highly optimized for bundle size. - -### Recoil - -[Recoil](https://recoiljs.org/) is a state management library developed by Facebook, specifically designed for React applications. By utilizing atoms and selectors, Recoil allows you to efficiently manage state and derived state. Its key benefit is the ability to update components only when the state they're subscribed to changes, reducing unnecessary re-renders and keeping your application fast and efficient. Recoil also offers great developer experience with built-in debugging tools. - -Choose the library that best fits your requirements and project structure to ensure an efficient state management solution for your application. - -## ๐Ÿค– ChatGPT Code Review - -We've integrated the innovative [ChatGPT Code Review](https://github.com/anc95/ChatGPT-CodeReview) for AI-powered, automated code reviews. This feature provides real-time feedback on your code, helping improve code quality and catch potential issues. - -To use ChatGPT Code Review, add an `OPENAI_API_KEY` environment variable with an appropriate key from the OpenAI platform. For setup details, refer to the [Using GitHub Actions](https://github.com/anc95/ChatGPT-CodeReview#using-github-actions) section in the documentation. - -![image](https://user-images.githubusercontent.com/28964599/233685071-e1371edf-6359-41c3-a989-335d6ee09cb7.png) - -## ๐Ÿ’ป Environment Variables handling - -[T3 Env](https://env.t3.gg/) is a library that provides environmental variables checking at build time, type validation and transforming. It ensures that your application is using the correct environment variables and their values are of the expected type. Youโ€™ll never again struggle with runtime errors caused by incorrect environment variable usage. - -Config file is located at `env.mjs`. Simply set your client and server variables and import `env` from any file in your project. - -```ts -export const env = createEnv({ - server: { - // Server variables - SECRET_KEY: z.string(), - }, - client: { - // Client variables - API_URL: z.string().url(), - }, - runtimeEnv: { - // Assign runtime variables - SECRET_KEY: process.env.SECRET_KEY, - API_URL: process.env.NEXT_PUBLIC_API_URL, - }, -}) -``` - -If the required environment variables are not set, you'll get an error message: - -```sh - โŒ Invalid environment variables: { SECRET_KEY: [ 'Required' ] } -``` - -## ๐Ÿค Contribution - -Contributions are always welcome! To contribute, please follow these steps: - -1. Fork the repository. -2. Create a new branch with a descriptive name. -3. Make your changes, and commit them using the [Conventional Commits](https://www.conventionalcommits.org/) format. -4. Push your changes to the forked repository. -5. Create a pull request, and we'll review your changes. - -## Support - -If you're looking for help or simply want to share your thoughts about the project, we encourage you to join our Discord community. Here's the link: [https://blazity.com/discord](https://blazity.com/discord). It's a space where we exchange ideas and help one another. Everyone's input is appreciated, and we look forward to welcoming you. - -
- - Blazity Discord Banner - -
- - -## ๐Ÿ“œ License - -This project is licensed under the MIT License. For more information, see the [LICENSE](./LICENSE) file. - -## Contributors - - - - - - - - - - - - - - - - - -
Bart Stefanski
Bart Stefanski

๐Ÿ’ป
Jakub Jabล‚oล„ski
Jakub Jabล‚oล„ski

๐Ÿš‡
Igor Klepacki
Igor Klepacki

๐Ÿ“–
- - Add your contributions - -
- - - - - - - - -[check-workflow-badge]: https://img.shields.io/github/actions/workflow/status/blazity/next-enterprise/check.yml?label=check -[github-license-badge]: https://img.shields.io/github/license/blazity/next-enterprise?link=https%3A%2F%2Fgithub.com%2FBlazity%2Fnext-enterprise%2Fblob%2Fmain%2FLICENSE -[github-contributors-badge]: https://img.shields.io/github/contributors/blazity/next-enterprise?link=https%3A%2F%2Fgithub.com%2FBlazity%2Fnext-enterprise%2Fgraphs%2Fcontributors -[discord-badge]: https://img.shields.io/discord/1111676875782234175?color=7b8dcd&link=https%3A%2F%2Fblazity.com%2Fdiscord -[made-by-blazity-badge]: https://img.shields.io/badge/made_by-Blazity-blue?color=FF782B&link=https://blazity.com/ - -[check-workflow-badge-link]: https://github.com/Blazity/next-enterprise/actions/workflows/check.yml -[github-license-badge-link]: https://github.com/Blazity/next-enterprise/blob/main/LICENSE -[github-contributors-badge-link]: https://github.com/Blazity/next-enterprise/graphs/contributors -[discord-badge-link]: https://blazity.com/discord -[made-by-blazity-badge-link]: https://blazity.com/?utm_source=nextenterprise&utm_medium=github +Built on the [Next.js Enterprise Boilerplate](https://github.com/Blazity/next-enterprise) by Blazity. \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 8acd5b9..27b5590 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,5 @@ import "styles/tailwind.css" +import "styles/global.css" export default function RootLayout({ children }: { children: React.ReactNode }) { return ( diff --git a/app/page.tsx b/app/page.tsx index 3726b4b..29872af 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,29 +1,21 @@ import { Metadata } from "next" -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" -import { faXmark } from "@fortawesome/free-solid-svg-icons" import Form from "components/Form" +import Head from "next/head" export const metadata: Metadata = { title: "Form Components", } export default function Web() { + return ( <> + + +
- {/* Close button */} -
- -
- {/* Title */} -
-

Document Upload

-
- {/* Form layout */} -
-
diff --git a/components/Button/Button.stories.tsx b/components/Button/Button.stories.tsx deleted file mode 100644 index ad992c7..0000000 --- a/components/Button/Button.stories.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react" -import { Button } from "./Button" - -const meta: Meta = { - title: "Button", - component: Button, - args: { - intent: "primary", - underline: false, - children: "Button", - size: "lg", - }, - argTypes: { - intent: { - options: ["primary", "secondary"], - control: { type: "select" }, - }, - size: { - options: ["sm", "lg"], - control: { type: "select" }, - }, - }, -} - -type Story = StoryObj - -export const Default: Story = { - render: (args) => + ); +}; diff --git a/components/Form/ClientField.tsx b/components/Form/ClientField.tsx index da2bc68..2614492 100644 --- a/components/Form/ClientField.tsx +++ b/components/Form/ClientField.tsx @@ -1,34 +1,46 @@ +/** + * ClientField Component + * + * Renders a list of testing centers with associated client selection inputs. + * Each testing center allows for the selection of a client from a provided list. + */ + +import { faClock } from "@fortawesome/free-regular-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import React from "react" import SelectInput from "./SelectInput" import { Utils } from "./Utils" -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" -import { faClock } from "@fortawesome/free-regular-svg-icons" -// Define a type for your props to improve type-checking +/** + * Props for ClientField component. + * + * @param testingCenters Array of testing center names to be displayed. + * @param clients Array of client names available for selection. + */ + type ClientFieldProps = { - testingCenters: string[] - clients: string[] + testingCenters: string[] + clients: string[] } -// Destructure the props in the function parameter const ClientField: React.FC = ({ testingCenters, clients }) => { return ( -
+
+ {/* Render a selection input for each testing center, allowing a client to be chosen for each. */} {testingCenters.map((testingCenter) => ( -
+
{testingCenter} -
+
- - {clients.map((option: string) => ( - - ))} - - -
- + + {clients.map((option: string) => ( + + ))} + + +
))}
diff --git a/components/Form/ErrorMessage.tsx b/components/Form/ErrorMessage.tsx index 4b3534d..acf10f6 100644 --- a/components/Form/ErrorMessage.tsx +++ b/components/Form/ErrorMessage.tsx @@ -1,8 +1,14 @@ +/** + * ErrorMessage Component + * + * Displays an error message if a form field has been touched and an error exists. + */ + import React from "react" const ErrorMessage = ({ touched, error }: { touched: boolean; error?: string }) => { if (touched && error) { - return

{error}

+ return

{error}

} return null } diff --git a/components/Form/FileInput.tsx b/components/Form/FileInput.tsx index 25b7a3b..29a058d 100644 --- a/components/Form/FileInput.tsx +++ b/components/Form/FileInput.tsx @@ -1,64 +1,146 @@ +/** + * FileInput Component + * + * Renders a file input with drag-and-drop support. Displays the selected file's name and size, + * and simulates file upload progress. Integrates with Formik for form state management. + */ + +import { faFile } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useField, useFormikContext } from "formik"; -import React, { useRef } from "react"; +import React, { useRef, useState } from "react"; import ErrorMessage from "./ErrorMessage"; import RequiredAsterisk from "./RequiredAsterisk"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faFile } from "@fortawesome/free-solid-svg-icons"; - +import Separator from "./Separator"; interface FileInputProps { label: string; required?: boolean; - name:string; + name: string; } -const FileInput: React.FC = ({ label, ...props }) => { +const FileInput: React.FC = ({ label, ...props }) => { const { required } = props; const [field, meta] = useField(props); const { setFieldValue } = useFormikContext(); const fileInputRef = useRef(null); + // State to manage drag-over styling + const [dragging, setDragging] = useState(false); + + // State to store information about the selected file + const [fileInfo, setFileInfo] = useState<{ name: string; size: number } | null>(null); + + // State to simulate and display upload progress + const [uploadProgress, setUploadProgress] = useState(0); + + // Simulates file upload progress + const simulateUpload = () => { + let progress = 0; + const interval = setInterval(() => { + progress += 10; + setUploadProgress(progress); + if (progress >= 100) { + clearInterval(interval); + } + }, 20); // Adjust time interval to control speed of the animation + }; + + // Handles file selection via input change event const handleChange = (event: React.ChangeEvent) => { const { files } = event.target; const file = files ? files[0] : null; - setFieldValue(props.name, file); // Directly use setFieldValue to update Formik's state + setFieldValue(props.name, file); // Update Formik's state + if (file) { + setFileInfo({ name: file.name, size: file.size }); + setUploadProgress(0); // Reset progress + simulateUpload(); // Simulate upload process + } }; + // Opens the file input dialog const handleClick = () => { if (fileInputRef.current) { fileInputRef.current.click(); } }; + // Handles file drop const handleDrop = (event: React.DragEvent) => { event.preventDefault(); event.stopPropagation(); + setDragging(false); // Reset drag state const files = event.dataTransfer.files; if (files && files.length > 0) { - const file = files[0]; - setFieldValue(props.name, file); + const file = files[0]; + if (file) { + setFileInfo({ name: file.name, size: file.size }); + setFieldValue(props.name, file); + } + setUploadProgress(0); // Reset progress + simulateUpload(); // Simulate upload process + } +}; + +// Manages drag-over state for styling + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + if (!dragging) { + setDragging(true); } }; + // Resets dragging state when dragging leaves the drop area + const handleDragLeave = (event: React.DragEvent) => { + event.preventDefault(); + setDragging(false); + }; + return ( -
- -
e.preventDefault()}> -
- -

Drag & Drop Here or Browse

-
- +
+ {/* Title */} +
+ +
- + + {/* File drop area */} +
+
+ +

Drag & Drop Here or Browse

+
+ +
+ + {/* Hidden input */} + + + {/* File info */} + {fileInfo && ( +
+ +
+
+

{fileInfo.name}

+

{Math.round(fileInfo.size / 1024)} KB

+
+
+
+
+
+
+ )} +
); diff --git a/components/Form/RadioGroup.tsx b/components/Form/RadioGroup.tsx index 9886d69..d077c79 100644 --- a/components/Form/RadioGroup.tsx +++ b/components/Form/RadioGroup.tsx @@ -1,39 +1,63 @@ +/** + * RadioGroupField Component + * + * Renders a group of radio buttons using Radix UI with Formik integration. + * Supports custom options, labels, and an optional separator. + */ + +import * as RadioGroup from "@radix-ui/react-radio-group" import { useField } from "formik" import React from "react" import ErrorMessage from "./ErrorMessage" import RequiredAsterisk from "./RequiredAsterisk" -import * as RadioGroup from "@radix-ui/react-radio-group" +import Separator from "./Separator" -const RadioGroupField = ({ +/** + * Props for RadioGroupField. + * + * @param name The name attribute for the radio group, used for form submission. + * @param label Display label for the radio group. + * @param separator Optional boolean to render a visual separator above the radio group. + * @param options Array of option strings that the radio group will display. + * @param props Additional props passed to the radio group, including `required`. + */ + +interface RadioGroupFieldProps { + name: string; + label: string; + separator?: boolean; + options: string[]; + [key: string]: any; // This allows for additional props not explicitly defined in the interface. +} + +const RadioGroupField: React.FC = ({ name, label, + separator, options, ...props -}: { - name: string - label: string - options: string[] - [x: string]: any // Allow any other props }) => { const { required, ...restProps } = props + + // State and helpers from Formik for managing radio group value const [field, meta, helpers] = useField({ ...restProps, name }) + // Handles radio option change, updating Formik's state const handleChange = (value: string) => { helpers.setValue(value) } return ( -
- - - +
+
+ {separator && } + +
+ + {options.map((option) => (
- +