Task Manager is a personal chore manager.
I would like to express my sincere gratitude to Hashnode and Appwrite for providing this incredible opportunity. Participating in this event has been an enriching learning experience and a true privilege.
What is Task Manager?
Task Manager is a user-friendly application designed to help you effortlessly manage your daily tasks. Its minimalist layout and intuitive drag-and-drop feature allow you to easily organize your tasks into three columns: To-Do, In Progress, and Done. The application also offers both light and dark modes to suit your personal preferences. It is also completely responsive so that it can be used on screens of any size. This provides a seamless user experience and helps you stay on top of your responsibilities. Additionally, it has complete CRUD functionality, i.e. you have the option to create new tasks, read them, edit, and delete them too. Because of how awesome Appwrite is, Task Manager also has the option to upload images with the task and the image is stored in appwrite's storage.
Why did I choose this project?
I was inspired to create a chore list manager for the Appwrite Hackathon on Hashnode because I saw an opportunity to make a meaningful impact on people’s daily lives. By providing a tool to help manage day-to-day chores, and by making the code accessible and well-documented for beginners and curious learners alike, I hope to contribute to the community in a valuable way.
While perusing Appwrite’s GitHub repository, I discovered that their only existing to-do list project, built with Next.Js, was outdated and hadn’t been updated in 2 years. I seized the opportunity to create a modern, up-to-date version using Next 13.4.4 with an app directory structure. My project features a light/dark mode toggle and employs Zustand for state management, resulting in a sophisticated and user-friendly experience.
What is the tech stack used?
When building an application, it’s important to carefully choose the technologies and tools that will be used in its development. In this section, I will take a closer look at the tech stack used to build Task Manager and explore how each technology contributes to the functionality and user experience of the app:
-
Next.js 13.4 /app directory as the React-based framework for building the application
-
Appwrite as the backend server for database and storage.
-
TypeScript for adding static typing to JavaScript
-
Tailwind CSS as the utility-first CSS framework for styling the application
-
Zustand for state management
-
React Beautiful DND for drag-and-drop functionality
-
Headless UI and Heroicons for building accessible UI components and icons
-
Radix UI for building dropdown menus
-
Lucide React for adding SVG icons
-
Next Themes for adding light and dark mode support
-
Hashnode for providing this opportunity and platform to share and write great articles.
Did I face any challenges while building the app?
Embarking on the journey of building an app is always an adventure filled with challenges and rewards. As I developed my chore list manager for the Appwrite Hackathon on Hashnode, I encountered numerous obstacles that tested my abilities and spurred me to expand my knowledge and skills as a developer.
From mastering the intricacies of drag-and-drop functionality to seamlessly integrating appwrite’s cutting-edge backend-as-a-service technology, each step presented its own unique hurdles.
Yet, with unwavering determination and steadfast perseverance, I triumphed over these challenges and crafted an app of which I am immensely proud. Prior to this project, I had no experience with appwrite or React Beautiful DND, but the thrill of learning how to incorporate these tools was exhilarating.
The knowledge I gained has left me confident in my ability to utilize them in future projects, particularly appwrite, with its user-friendly interface and ability to simplify complex tasks.
As I neared the end of my development journey for the Appwrite Hackathon on Hashnode, I encountered a particularly perplexing challenge: an “Error: React.Children.only expected to receive a single React element child” message. This error was especially confounding because the application had been functioning flawlessly just 10 minutes prior. In the past, I had made the mistake of rendering multiple children, but I had since learned from that experience and paid special attention to avoiding it. After spending over an hour scrutinizing my code for any logical explanation for the error and double-checking every return statement in my application, I was still at a loss. So, I resorted to the tried-and-true method of trial and error.
The steps I took which finally guided me to the problem were:
-
Experiment with the various components in my layout.tsx file.
-
I soon discovered that removing the Navbar eliminated the error.
-
This led me to investigate the Navbar component more closely, which was connected to four other components: MobileMenu.tsx, MenuItem.tsx, SocialIcons.tsx, and ThemeToggle.tsx.
-
After some further tinkering, I realized that the issue originated from ThemeToggle.
-
The only recent change I made to this component was commenting it out. So, I consulted my GitHub repository to review my previous commits and replaced the content of ThemeToggle with an earlier, uncommented version.
Voila, the error disappeared.
This was one of the many problems I faced and took it as a learning experience. Got more mountains to climb just can't give up.
Setting Up the Task Manager App in Your Local Environment: A Step-by-Step Guide
Here is the link to the public GitHub repo. Here are the steps you can follow to fork, clone, and run the project in your local environment:
-
Fork the repository on GitHub to create a copy of the project in your own account.
-
Clone the forked repository to your local machine using git clone
.
-
Navigate to the project directory and run npm i
to install all the necessary dependencies.
-
Set up the environment variables for Appwrite by copying the .env.example
file to a new file named .env.local
and filling in the values for your Appwrite instance, including the database, collection, and storage information. Below is a section called "Getting started with appwrite to create the database, collection, and storage" I will be explaining how you can set up your own database, collection, and storage in appwrite.io to get those values.
-
Once the dependencies are installed and the environment variables are set up, run npm run dev
to start the development server.
The project should now be running on your local machine and you can experiment with it and learn from the code.
To add a task, go to the Board page by clicking on the “Application” tab. You will see 3 columns. Click on the “+” icon and a modal will appear where you can add your task and an optional image. Once you have added your task, there are 2 options available on the Task card: Edit and Delete.
Behind the Scenes: Understanding the Code for Task Manager
The app was built using Next.js 13.4.4 and React 18.2.0, with Zustand 4.3.8 for state management. I integrated Appwrite 11.0.0 as the backend-as-a-service provider to handle database and image storage. For the user interface, I used TailwindCSS 3.3.2 for styling, along with Headless UI 1.7.15 and Heroicons 2.0.18 for UI components. I also incorporated React Beautiful DND 13.1.1 to implement the drag-and-drop functionality. The code was written in TypeScript 5.0.4.
What is state management?
State management refers to the way an application handles and manages changes to its data over time. In the context of this application, state management involves keeping track of the data that drives the user interface and updating the UI whenever that data changes.
Zustand is a small, fast, and easy-to-use state management library for React. It allows you to create global or local stores for your application’s state and provides a simple API for updating and accessing that state. With Zustand, you can easily manage the application’s state and ensure that your UI always reflects the latest data.
Leveraging Zustand for State Management in Task Manager App
When building my Task Manager app for the Appwrite-Hashnode hackathon, one of the key decisions I had to make was how to handle state management. After considering several options, I ultimately decided to use Zustand, a small and easy-to-use state management library for React. I’ll share my experience using Zustand to manage the state of my app and explain how it helped me create a seamless and user-friendly experience.
If you go those my repo you will notice a file called "BoardStore.tsx" in the store folder at the root of the project. That file exports a useBoardStore
hook that is created using the create
function from Zustand. This hook returns an object containing the board state and several functions for updating and managing that state.
The board state is initialized with an empty map of columns, and several other pieces of state are also defined, including newTaskInput
, newTaskType
, and image
.
Several functions are defined for updating the board state and interacting with Appwrite. For example, the getBoard
function retrieves the current board state from Appwrite using the getTaskByColumn
function and updates the local board state with the result. The deleteTask
function deletes a task from both the local board state and from Appwrite by calling the deleteFile
and deleteDocument
methods from Appwrite’s storage and database APIs.
Other functions are defined for updating various pieces of state, such as setNewTaskInput
, setNewTaskType
, and setImage
. The updateTodoInDb
function updates a chore item on Appwrite by calling the updateDocument
method from Appwrite’s database API. The addTask
function adds a new task to both the local board state and to Appwrite by calling the createDocument
method from Appwrite’s database API.
Overall, the hook demonstrates how Zustand can be used in conjunction with Appwrite to manage an application’s state and interact with Appwrite’s APIs to perform CRUD operations on data.
Usage of Appwrite
Appwrite has played a crucial role in the functioning of this application. I used appwrite's database to store my task and storage to upload images.
Fun Fact: In Task Manager, you also have the option to upload images
with your task.
Appwrite’s database API uses to store and manage the data for the tasks, appwrite has been really well documented on the official website. This allowed me to easily perform CRUD operations on the data and keep it in sync with the user interface. I also used Appwrite’s storage API to handle image uploads for tasks that included images.
Overall, Appwrite’s backend-as-a-service technology provided a solid foundation for my app and allowed me to focus on building a great user experience without having to worry about managing the backend infrastructure.
Getting started with appwrite to create the database, collection, and storage.
Setting up a database with appwrite is very simple and to the point. I used the following steps to create it.
-
Go to appwrite.io. If you don't have an account sign up. Once you are signed in you will be asked to create a project, enter the name you want your project to be. In my case 'task_manager" and then click on "Create project".
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685705790143/ff36b6a1-b9d5-4f7d-86aa-9a0686151d79.jpeg align="center")
-
Then I choose the platform, in my case web app.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685706079553/7dca8bb8-3a47-4cb3-bcad-f2ce5d31ca5f.jpeg align="center")
-
Then a Form will appear where you need to provide the name of your web application and hostname. For hostname I choose localhost since before deploying I will be developing in the local environment.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685706243691/35708975-0f88-4dc6-bda7-08dddc7105a8.jpeg align="center")
-
Then do as it says there, and download the sdk. For me, it was npm install appwrite
-
Then initialize it, for me it was to create a file called appwrite.ts inside the lib folder and copy and paste the code provided.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685706494149/c27dac00-fd6c-4450-a5ff-8df062963b9d.jpeg align="center")
-
The number you see here is the project id, if you are following my repo. Put that value on your .env.local file for "NEXT_PUBLIC_APPWRITE_PROJECT_ID="
Setting up the database and collection
Once you click on Next, it should redirect you to your dashboard. If you look at the left side bar you will see the option called "Databases"
-
Click on that option.
-
Click on "Create database'
-
Provide a name to your liking for me it is "task_manager"
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685706892007/7e974666-35a4-4704-87be-c8d133892077.jpeg align="center")
-
Then we will need to create the collection. After clicking on Create on top, you should be inside the database. Congrats you created a database. Yes, it is this easy with appwrite.
Now there should be an option to create a collection. So click on "Create Collection".
-
Provide a name to your liking, for me, it is 'task'
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685707118843/50efdfe9-d92b-4d0a-bdfa-6d1673ea3045.jpeg align="center")
-
Once you click on Create you should be inside the collection. Out here now we will define the schema.
So now click on Attributes.
-
The first schema will be for the title (Title for the task) so once we click on "Create Attribute" and form like this should appear. Fill in based on your requirements. For me, the title should be a string and required. Size is the number of characters.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685707453242/89fbb299-72cc-497d-8c7d-4dbe05952174.jpeg align="center")
-
Once you click on create your attribute is created. Then I created another attribute for the status of the task and another for the image URL. For status, I used ENUM so the user of the app can choose between todo, in-progess, and done. For the image attribute, I used string just like the first attribute.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685707985014/bbd74ab8-2768-424f-b0e5-79a54ad83afa.jpeg align="center")
-
Here is a screenshot of the final 3 attributes for my task.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685770124598/bd711b9f-ad83-4d37-af55-010dafd8a9dd.jpeg align="center")
-
Now you can click on the Task_Manger option on top and get the database ID and collection ID and update your env file. The Database ID value will be for "NEXT_PUBLIC_DATABASE_ID=" and the Collection ID value will be for "NEXT_PUBLIC_TODOS_COLLECTION_ID="
This is how I created my database and collection and defined my collection schema.
Now setting up the storage to store images
So our images will be stored in appwrite's storage. For that, we will need to create a bucket. I followed the following steps to create my bucket.
-
Click on the storage option on the left sidebar.
-
Click on "Create bucket", and a form appeared. I named my bucket "images".
That was it, my storage bucket was successfully created. Now copy the bucked id and paste it as a value for "NEXT_PUBLIC_IMAGE_BUCKET_ID=' in the env file.
Once all the env values are set up our initiation is done.
Note: For testing purposes, we can manually provide a document, for that, we need to go to collection --> Create Document. Add the title and enum value and then click on Next. In this screenshot, I created a document with the title "Test Todo". If you do not provide an enum value there a new column will be created in the front end side.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685786210516/0443a541-37ce-470b-9712-50d06bd22766.jpeg align="center")
This above step is completely optional since we can Create a Task from the front end by clicking on the "+" icon which is available in each column.
Until now I have explained the setup and why I used Zustand for state management.
But how is the application communicating with appwrite's database?
It is communication with the help of appwrite.ts (lib --> appwrite --> appwrite.ts) file that I mentioned above. The thing the code is doing is a new instance of the Databases
class is created by passing in a configured Client
instance as an argument. The Client
instance is configured with the endpoint and project ID from the environment variables. The Databases
instance is then used to interact with the Appwrite's database.
Now we are clear on how the application is communicating with the database. But,
How is the Board getting the data for the columns?
That has been made possible with the code inside the '/lib/getTaskByColumn.ts' file.
<mark>Brief:</mark> The code defines a function that is grouping and sorts tasks by Status using Appwrite’s listDocuments Method.
<mark>Explanation:</mark> The code exports an asynchronous function named getTaskByColumn that retrieves data from a database using the listDocuments method of the databases object imported from “@/lib/appwrite/appwrite”. The data is then processed to group the task by their status and create a Map object with keys as todo status and values as objects with id and todos properties. The code also checks if all column types (“todo”, “in progress”, “done”) are present in the columns Map object and adds any missing column types with empty todos. Finally, the columns Map object is sorted based on the order of column types in the columnTypes array and a board object is created with a columns property set to the sorted columns Map object. The board object is then returned by the function.
How are images getting uploaded to Appwrite storage?
That has been made possible with the code inside the '/lib/uploadImage.ts' file.
<mark>Brief:</mark> This code defines a function that uploads a file to Appwrite storage and returns the uploaded file.
<mark>Explanation:</mark> The code imports the ID
and storage
modules from @/lib/appwrite/appwrite
and defines an asynchronous function called uploadImage
that takes in a File
object as a parameter. The function checks if the file is provided and, if it is, uses the createFile
method of the storage
object to upload the file. The createFile
method takes in three arguments: the bucket ID, the file ID, and the file object. The bucket ID is retrieved from the environment variables and the file ID is generated using the unique
method of the ID
module. The uploaded file is then returned by the function. In summary, this code defines a function that uploads a file to Appwrite storage and returns the uploaded file.
How is the image URL getting retrived?
That has been made possible with the code inside the '/lib/getUrl.ts' file.
<mark>Brief:</mark> This code defines a function that retrieves the URL of an image stored in Appwrite storage.
<mark>Explanation:</mark> The code imports the storage
module from @/lib/appwrite/appwrite
and defines an asynchronous function called getUrl
that takes in an Image
object as a parameter. The function uses the getFilePreview
method of the storage
object to get the URL of the image. The getFilePreview
method takes in two arguments: the bucket ID and the file ID. These values are retrieved from the image
object passed into the function. The URL is then returned by the function. In summary, this code defines a function that retrieves the URL of an image stored in Appwrite storage.
Exploring My GitHub Repository Further: A Guide to Understanding the Functionality of Each File
In this section, I will provide a brief explanation of the functionality of each file in my repository.
-
app folder
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685806771021/b5381f54-3db1-4c83-969e-05397ea39b64.jpeg align="center")
Routing with the app
directory is controlled via the folders inside of it. The UI for a particular route is defined by a page.tsx
file inside the folder.
I will explain the files from top to bottom (according to the screenshot above)
<mark>application/page.tsx:</mark> This file is responsible to render the UI for the www.example.com/application
route. Inside that file, I am importing a Board
component from “@/components/TaskBoard/Board” and rendering it.
<mark>error.tsx:</mark> This file automatically wraps the page inside of a React error boundary. Isolate errors to affected segments while keeping the rest of the app functional. Add functionality to attempt to recover from an error without a full page reload.
<mark>global.css:</mark> There I am defining the global styling. Since I used Tailwind, I have added the Tailwind directives here and also @font-face
rules that define custom fonts for the application.
<mark>layout.tsx:</mark> This file defines the root layout for an application. The RootLayout
component is typically used to define the root layout for an entire Next.js application. I have used it to wrap all pages in the application and to also define global styles, metadata, and components that should be rendered on every page.
<mark>loading.tsx:</mark> This file helps to create meaningful Loading UI with React Suspense.
<mark>page.tsx:</mark> This page is responsible for rendering the UI for the Home Page of the application. Out here I have imported a component called <Hero/> from the components folder.
-
assets folder
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685808120438/094aff20-bb40-4ebd-b022-39c7dbdffc36.jpeg align="center")
This folder just contains another folder called '/fonts" just contains custom fonts I used in the application.
-
components folder
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685808527047/8bb07dec-6acc-4598-86c0-1e165ea1d2df.jpeg align="center")
This folder is used to organize and store reusable React components and I have used it for this exact purpose.
-
modal folder: Contains the Modal components used in the application.
<mark>ConfirmTaskModal.tsx:</mark> This file contains the code which defines a ConfirmTaskModal
component that appears for confirming the deletion of a task, the trashcan icon on the task card.
<mark>EditTaskModal.tsx:</mark> This file contains the code which defines an EditTaskModal
component that appears when someone clicks on the edit option, the pencil icon on the task card.
<mark>Modal.tsx:</mark> This code defines a Modal
component that appears to be a reusable modal dialog component for the application. The component imports several dependencies, including Fragment
, Dialog
, Transition
from @headlessui/react
, and X
from “lucide-react”.
-
Navbar folder: Contains the Navbar component used in the application.
<mark>MobileNav.tsx:</mark> Defines a MobileMenu
component that is the mobile navigation menu for the application.
<mark>Navbar.tsx</mark><mark>:</mark> Defines a Navbar
component that is the main navigation menu for the application. This file also contains the breakpoint logic based on screen size to either display this navigation menu or the mobile navigation menu.
<mark>MenuItem.tsx:</mark> Defines a MenuItem
component that appears to be a reusable menu item component.
-
TaskBoard Folder: Contains the components used in the /application route.
<mark>Board.tsx</mark><mark>:</mark> This code defines a task board component for the application that uses drag-and-drop functionality to manage tasks.
The Board
component is defined as a functional component that uses the useBoardStore
hook to retrieve several values and functions from the board store. These include the current board state, the getBoard
function for retrieving the board state from the external database, the setBoardState
function for updating the local board state, and the updateTodoInDb
function for updating a task in the external database.
<mark>Column.tsx:</mark> This code defines a column component to use in a task board within the application.
The Column
component is defined as a functional component that accepts several props, including id
, todos
, and index
. These props are used to provide information about the column, including its id, the tasks within the column, and its index within the task board.
Within the component, the setNewTaskType
function is retrieved from the useBoardStore
hook and the openModal
function is retrieved from the useModalStore
hook. The component also defines a local handleAddTodo
function that calls these functions to open a modal for adding a new task to the column.
The tasks within the column are defined using the TodoCard
component and are rendered using the .map()
method on the provided todos
prop. The column also includes an add button at the bottom right corner of the column. The add button calls the local handleAddTodo
function when clicked.
<mark>Modal.tsx:</mark> This code defines a modal dialog component for adding a new task in the application.
The modal content includes an input field for entering a new task description and a radio group for selecting the new task type. The modal also includes an upload button for uploading an image to be associated with the new task. The upload button uses a hidden file input element to trigger a file picker dialog when clicked.
<mark>TaskTypeRadioGroup.tsx:</mark> This code defines a radio group component for selecting the type of a new task in the application.
This component is defined as a functional component that uses the useBoardStore
hook to retrieve the current new task type value and the setNewTaskType
function for updating the new task type value.
<mark>TodoCard.tsx:</mark> This code defines a task card component for use in a task board in the application.
Within the component, two local state variables are defined using the useState
hook: one for the image URL of the task and another for the confirmation modal open status. The component also uses the useEffect
hook to fetch the image URL of the task from the server when the component mounts.
The component returns a fragment that contains two modal components and a nested div
element for displaying the task card content. The modal components include a confirmation modal for deleting the task and an edit modal for editing the task. The task card content includes the task title and two buttons for opening the edit and delete modal.
-
Theme folder: Contains the theme components used in the application to change the theme. The 3 theme available in the application is Light, Dark, and System.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685811197726/17d46424-8563-4ad6-b6f1-129f39d42f8d.jpeg align="center")
<mark>DropdownMenu.tsx:</mark> This code defines the components for use in a dropdown menu system in the application when someone wants to change the theme.
<mark>Providers.tsx:</mark> This code defines a provider component for use in the application that provides theme support using the next-themes
package.
Within the component, a ThemeProvider
element is returned that wraps the provided children
prop.
The ThemeProvider
element is configured with several props, including attribute="class"
, defaultTheme="system"
, and enableSystem
. These props configure the theme provider to use the class
attribute for applying theme styles, to use the system theme as the default theme, and to enable automatic switching between light and dark themes based on the user’s system preferences.
<mark>ThemeToggle.tsx:</mark> This code defines a dropdown menu component for toggling the theme in the application.
The ThemeToggle
component is defined as a functional component that uses the useTheme
hook to retrieve the setTheme
function for updating the current theme. The component returns a DropdownMenu
element that contains several child elements for displaying the dropdown menu content.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685811552019/23ee898d-c204-4f6a-8e34-1086cf4746f5.jpeg align="center")
<mark>Button.tsx:</mark> This code defines a button component for use in the application that supports several style and size variants.
The code defines a buttonVariants
object using the cva
function from the class-variance-authority
package. This object is used to define several variants for the button component, including variants for the button style and size. The object also defines default variants for the button style and size.
The Button
component is defined as a functional component that accepts several props, including className
, children
, variant
, isLoading
, and size
. These props are used to provide information about the button, including its style, content, loading state, and size.
<mark>Hero.tsx:</mark> This code defines a hero section component for use in the application that displays several headings and two buttons for navigation.
The company is responsible for the UI on the Home page.
<mark>Icons.tsx:</mark> This code defines a collection of icon components for use in the application.
The code defines an Icons
object that maps several icon names to their corresponding icon components. The object also includes a default export that exports the entire Icons
object.
<mark>SocialIcons.tsx:</mark> This code defines a component for displaying social media icons in the application.
-
lib folder
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685942346812/1e1912df-5aff-49d8-852e-d9661acf62f3.jpeg align="center")
I have explained what the files inside the lib folder do above in the "But how is the application communicating with appwrite's database?" section. So out in this section, I will only provide a brief explanation.
<mark>appwrite/appwrite.ts:</mark> The code snippet is setting up and configuring the Appwrite SDK for the application.
<mark>getTaskByColumn.ts:</mark> The code defines a function that is grouping and sorts tasks by Status using Appwrite’s listDocuments Method.
<mark>getUrl.ts:</mark> This code defines a function that retrieves the URL of an image stored in Appwrite storage.
<mark>uploadImage.ts</mark><mark>:</mark> This code defines a function that uploads a file to Appwrite storage and returns the uploaded file.
<mark>utils.ts:</mark> This code snippet defines a utility function cn
that takes in an array of class names as input and returns a merged class name string. The function uses the clsx
function from the clsx
package to concatenate the input class names into a single string, and then uses the twMerge
function from the tailwind-merge
package to merge the class names and resolve any conflicts between them. This can be useful when working with Tailwind CSS to combine multiple utility classes into a single class name string.
-
store folder
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685812300732/536a8168-3018-49ec-b35d-fdd75e61fdef.jpeg align="center")
<mark>BoardStore.tsx:</mark> This code defines a state management hook for managing the state of a board component in the application.
The useBoardStore
hook is defined using the create
function from the zustand
package. The hook is initialized with an object that defines the initial board state and includes functions for updating the board state. The hook also includes several functions for interacting with the Appwrite server to perform CRUD operations on the board data.
<mark>ModalStore.tsx:</mark> this code defines a state management hook for managing the open state of a modal component in the application.
Now I will explain what the other files are doing.
![](https://cdn.hashnode.com/res/hashnode/image/upload/v1685942779156/86e5095f-1d44-4be5-89bd-0abdd282c847.jpeg align="center")
<mark>.env.example</mark>: The file contains a list of environment variables that need to be set for the application to function correctly. These variables include the Appwrite project ID, database ID, collection ID, and image bucket ID. The values can be obtained from appwrite's console as explained above.
<mark>.eslintrc.json:</mark> The file specifies that the project extends the next/core-web-vitals
ESLint configuration, which is a built-in configuration provided by Next.js that includes rules for enforcing best practices for improving Core Web Vitals.
<mark>.gitignore:</mark> This file contains info about the file git should ignore while committing changes.
<mark>LICENSE:</mark> This file contains info about the license this project is providing. It provides MIT license.
<mark>package-lock.json:</mark> This file is automatically generated by the npm package manager when you install packages using npm install
. This file contains a detailed record of the exact versions of all the packages that were installed, along with their dependencies. The purpose of this file is to ensure that when you run npm install
again, either on your own machine or on another machine, npm will install the exact same versions of the packages that were originally installed.
<mark>package.json:</mark> It contains metadata about the project, such as its name, version, and description, as well as a list of its dependencies and scripts. The dependencies are specified as a list of package names and version ranges and can be installed using the npm install
command.
<mark>postcss.config.js:</mark> This is a configuration file for PostCSS. PostCSS is a tool for transforming CSS using JavaScript plugins. This file specifies that the project is using two PostCSS plugins: tailwindcss
and autoprefixer
. The tailwindcss
plugin is used to process Tailwind CSS directives and generate the corresponding CSS styles. The autoprefixer
plugin is used to automatically add vendor prefixes to CSS rules, which can help improve cross-browser compatibility. This configuration file is used by PostCSS to determine which plugins to use when processing the project’s CSS files.
<mark>tailwind.config.js:</mark> This is a configuration file for Tailwind CSS. This file specifies various configuration options for Tailwind CSS, such as the darkMode
option, which is set to ["class"]
to enable dark mode support using the class
strategy. The content
option specifies the paths to the files that Tailwind should scan for class names. The theme
option is used to extend the default Tailwind theme by adding custom animations, keyframes, and font families. The plugins
option is an empty array, indicating that no custom plugins are being used. This configuration file is used by Tailwind CSS to generate the project’s CSS styles.
<mark>tsconfig.json:</mark> This is a configuration file for the TypeScript compiler. This configuration file is used by the TypeScript compiler to compile the project’s TypeScript code into JavaScript.
<mark>typings.d.ts:</mark> This is a TypeScript declaration file that defines custom types and interfaces for a project. This file defines several interfaces and types that describe the shape of data used in a "Task Manager" application.
A brief on styling and responsiveness of the application
In the project, I used tailwind css to do all the stylings.
I also used custom fonts from FontShare for the application, the name of the font families are Satoshi and Ranade.
The application also features Light and Dark mode which has been utilized with the use of the class
strategy.
<mark>Background</mark>
Light mode: I have used a shade of white [#e5e5e5] as the background color.
Dark mode: I have used a shade of black [#0f172a] as the background color since pure black might cause eye strain to some users.
<mark>Home page</mark>
There is a Hero Message called "Effortlessly manage your day-to-day task with Task Manager". I have used bg-clip-text
and text-transparent
so that the following properties apply the gradient to the text itself rather than the background of the element.
Responsiveness: text-4xl
, md:text-5xl
, and lg:text-6xl
classes applied to the hero message. The size change with the different breakpoint.
Light mode: I have used a gradient that goes from bottom to top and left to right (bg-gradient-to-bl
) and transitions from the color slate-900
to the color gray-500
.
Dark mode: I have used a gradient that goes from top to bottom and left to right (bg-gradient-to-tl
) and transitions from the color indigo-900
to the color purple-500
For the words "Task Manager" I made added a span element to it so that I can provide custom animation and color.
Light mode: I have used a gradient effect (bg-gradient-to-r
) that goes from left to right and transitions from the color yellow-500
to pink-500
and then to orange-500
.
Dark mode: I have used a gradient effect (bg-gradient-to-r
) that goes from left to right and that will transition from yellow-500
to purple-500
and then red-500
.
For the transition, I have also used animate-text
class which is a custom animation. It has been defined in the tailwind.config.js
file.
The animation
property specifies that the text
animation should have a duration of 5 seconds (5s
), use an ease
timing function, and repeat infinitely (infinite
).
The keyframes
property defines the keyframes for the text
animation. At the beginning (0%
) and end (100%
) of the animation, the background-size
is set to 200% 200%
and the background-position
is set to left center
. At the halfway point (50%
) of the animation, the background-size
remains the same but the background-position
changes to right center
.
<mark>Application page</mark>
This page contains our Task Board. The color of the board and components changes with both light and dark modes. The layout of the Board also changes with different breakpoints making it responsive to different screen sizes. The 2 components that make up the Board are Columns and Task Cards.
Responsiveness of the Board: The Board is a grid container with one column on small screens and three columns on medium and larger screens. There will be a gap between grid items and the maximum width of the component will be limited. The component will also be centered horizontally within its parent.
Column: It has a top and bottom margin of 1.5rem, a medium-sized border-radius, a border around the element with a thicker left border, and padding on all sides. The left border has a width of 4px.
Light Mode: The color of the border is slate-900**.**
Dark Mode: The color of the border is set to neutral-100.
Task Cards: Has a medium-sized border radius and drop shadow, add vertical space between child elements, and set the width of the left border to 4px.
Responsiveness of the card: It is a flex container with a paragraph displaying the value of todo.title
and two buttons with icons i.e. Pencil Icon to open the Edit Modal and a Trash Icon to delete the card. Both the icons have a hover effect too. The flex container has column direction on medium to larger screens (md:max-xl:flex-col
). The flex items within the container are justified and aligned to the center (justify-between items-center
).
Light Mode: The background color is set to neutral-300
, and the border is set to slate-700
. For even-numbered child elements, the border color will be set to neutral-200
and the background color will be set to emerald-300
.
Dark Mode: The background color is set to neutral-300
, and the border is set to red-900
with 50% opacity. For even-numbered child elements, the border color will be set to #8BD3E6
with 80% opacity.
Public Repo Link
Github Repo: https://github.com/trace2798/appwrite_hackathon
Demo Video Link
Youtube Link: https://www.youtube.com/watch?v=NE7nh9qsQVw&feature=youtu.be
I hope this article helped you. If you have any questions feel free to leave a comment and I will respond back as soon as possible.
Happy Hacking !!!