Build a Feature-Rich University Library Management System
Build and Deploy a Fullstack App with Admin Dashboard | Next.js, PostgreSQL, Redis, Auth.js
Estimated read time: 1:20
Learn to use AI like a Pro
Get the latest AI workflows to boost your productivity and business performance, delivered weekly by expert consultants. Enjoy step-by-step guides, weekly Q&A sessions, and full access to our AI workflow archive.
Summary
Dive into an extraordinary tutorial that takes your web development skills to the next level. This video, hosted by JavaScript Mastery, guides you through the entire process of building a robust production-grade application—an interconnected University Library management system using Next.js, PostgreSQL, Redis, and more. Learn to implement industry-standard practices, including advanced authentication, caching, and error handling, all while focusing on user engagement through automated workflows with custom notifications. This tutorial is perfect for developers looking to build applications that impress not just on the surface but through their smart functionalities.
Highlights
Kickstart your project with a library system tutorial that goes beyond basic CRUD functionalities. 🚀
Develop a complex University Library management system using a monorepo with interconnected apps. 💡
Learn to secure your application with rate limiting and DDOS protection, safeguarding against malicious attacks. 🔒
Implement a smooth user onboarding process with personalized notifications, increasing user engagement. 📲
Discover how to make your app truly production-ready with serverless databases and efficient caching strategies. ☁️
Key Takeaways
Learned to develop a complete library management system with Next.js, PostgreSQL, Redis, and more, focusing on real-world app demands. 📚
Implemented advanced features like rate limiting, DDOS protection, automated workflows, and complex database queries. ⚙️
Explored an admin interface for managing data and monitoring user activities—a crucial aspect of enterprise apps. 🛠️
Understood the importance of engaging users through personalized notifications and emails, similar to industry leaders like Duolingo. 📧
Gained insights into setting up scalable production environments using Neon and Upstash for databases and caching. 🏗️
Overview
This comprehensive tutorial led by JavaScript Mastery is not just about jumping into another typical project. It's about taking you beyond the basics to build an app that truly impresses with deep functionalities. Starting with a library management system, you're learning to handle more than just CRUD operations.
With the aim of teaching you industry-standard practices, this video demonstrates how to integrate complex features like rate limiting, DDOS protection, and automated workflows. It goes even further with thoughtful insights into database handling, allowing you to manage users and content seamlessly.
Moreover, demonstrating practical deployment strategies, this tutorial covers how to make your application production-ready with tools like Neon and Upstash for scalable databases and caching. Whether you're here to enhance your skills or to scale up a project, this guide covers it all.
Chapters
00:00 - 00:30: Introduction The introduction highlights a common issue with tutorials that start strong but end with mundane projects. These projects often showcase simple CRUD functionalities and basic design, which may not be impressive. The chapter aims to address this by guiding the reader to build and deploy a production-grade University Library Management System. This system involves two interconnected applications organized in a monorepo, offering a comprehensive learning experience in industry-standard practices.
00:30 - 04:00: Initial Setup and Planning The chapter covers a comprehensive guide to setting up and planning for a web application. It highlights critical elements like rate limiting, DDOS protection, caching optimizations, handling multimedia uploads, managing complex database queries, and advanced error handling. Moreover, it delves into building automated workflows with custom notifications, ensuring that crucial real-world application features are addressed.
04:00 - 12:20: Odd Page Implementation The chapter titled 'Odd Page Implementation' delves into the management features and user interfaces of a library application. It highlights book detail pages that track the availability of books, display summary videos, and suggest similar books. Users have profile pages to manage their account, which includes tracking borrowed books and downloading receipts. In addition to user interfaces, the chapter discusses the development of enterprise applications that include admin interfaces. These are crucial for managing the data displayed on the public website. A significant focus is on creating another app with an analytics dashboard. This dashboard shows various statistics including new users, book borrow requests, and more. There are also admin functionalities through an all users page and account requests page where admins can approve requests.
12:20 - 23:40: Backend and Database Setup This chapter covers the backend and database setup for a project using Next.js and TypeScript. It includes creating pages for adding and editing book details, and managing borrow requests. The chapter emphasizes the use of full pagination and search on nearly every page. It also introduces Postgres with Neon, highlighting its speed, scalability, and its ability to support team collaboration and database management through branching.
23:40 - 28:40: Implementing Authentication The chapter discusses the integration of Git Image Kit for real-time media processing and asset management. It highlights the features of free storage, dynamic transformations, and streaming capabilities, enabling effective optimization and transformation of images and videos for any device. This makes it a comprehensive solution for delivering, managing, and collaborating on media assets at scale. Furthermore, the use of upstash reddis is emphasized for efficient caching and rate limiting, as well as constructing detailed workflows with custom triggers. Drizzle, described as the hottest ORM, is mentioned as a tool for accelerating database interactions.
28:40 - 41:00: Admin Dashboard Layout The 'Admin Dashboard Layout' chapter presents a demonstration of building a modern, polished app using tailwind CSS. It begins with a tour of the app expected to be developed, highlighting the user journey upon visiting the site initially. Users are directed to a sign-in screen, and if they need to create a new account, they proceed to a detailed sign-up page with comprehensive validation. There is also an emphasis on an image uploading feature with a completion percentage display, requiring users to upload their University card. The chapter concludes with steps following the account creation process.
41:00 - 51:40: Implementing CRUD for Books The chapter begins with the process that occurs behind the scenes when an account is created, highlighting the onboarding workflow initiated by the server. This workflow includes sending a welcome email to the new user.
51:40 - 59:40: Book Details and Borrowing This chapter describes the automation features introduced in a user-friendly library application. Upon launching the app, users are greeted with a modern homepage that highlights featured books with a 3D effect. Additionally, the library page provides a comprehensive list of available books. Users can conveniently search or filter the books, and clicking on a book leads to a detailed page showcasing information such as the total and available copies, descriptions, trailers, and summaries.
59:40 - 60:17: Conclusion and Next Steps The chapter discusses the process and reasoning behind restricting book borrowing privileges on a website until a user's account is approved by an administrator. This mimics real institutions' verification processes, such as university admissions. Additionally, there's an admin dashboard for managing user accounts and content, emphasizing a controlled and verified user experience.
Build and Deploy a Fullstack App with Admin Dashboard | Next.js, PostgreSQL, Redis, Auth.js Transcription
00:00 - 00:30 most tutorials start strong but then leave you in the shallow end with boring projects nice design same old crud functionalities simple pagination and call it a day might impress your mom but let's face it no one's buying those that's why today you'll build and deploy a production grade University Library management system which isn't just a single app but a set of two interconnected apps tied together into a monor repo these apps work seamlessly to teach you industry standard practices
00:30 - 01:00 from rate limiting DDOS protection caching optimizations multimedia uploads complex database queries and advanced error handling to automated workflows with custom notifications you'll learn to implement features that real world apps demand everything starts with a public facing app with an open source authentication with personalized onboarding flows and emails homepage with a highlighted book and newly added books with 3D effects a library page with a Advanced filtering and pagination
01:00 - 01:30 book detail pages that track availability and show summary video and similar books and a profile page to manage your account track borrowed books and download receipts but Enterprise apps also have admin interfaces for managing the data displayed on the public website you'll build it too a whole another app consisting of an analytics dashboard showing statistics New users and books borrow requests and more an all users page and account requests page where admin can approve or
01:30 - 02:00 revoke access all books page forms allowing you to add a new book and edit it book detail pages and a borrow request page that was a lot and yes with full pagination and search on practically every single page you'll do it all using nextjs typescript and next o for industry standard development postgress with neon the fastest Edge ready database built to scale allowing you to invite your team members and manage database using branches just like
02:00 - 02:30 you're working with Git image kit for real-time media processing apis and asset management offering free storage Dynamic Transformations and streaming capabilities perfect for optimizing and transforming images and videos for any device making it the only solution you'll need to deliver manage and collaborate on your media assets at scale up stash reddis for efficient caching rate limiting and the ability to build detailed workflows with custom triggers drizzle the hottest orm right now making database interactions faster
02:30 - 03:00 and simpler than ever and more like tailin CSS resend shaten to create a polished modern app but talk is cheap so let me show you a full demo of the app you'll build today when you visit the website for the first time you'll see the signin screen if you're creating a new account you can go to the signup page where you'll have to fill in a lot of information all of it fully validated we also have an image upload with completion percentage where you'll have to insert your University card that was quick once you've created an account
03:00 - 03:30 you'll see this nice little toast and you're in but behind the scenes something important has just happened the moment you've created an account our server triggered an onboarding workflow sending you a welcome email but that's not the best part another part of the onboarding workflow is inactivity checks so if you don't visit the website for the next 3 days you'll get another automated email prompting you to check it out and if you're already using it you'll receive a congratulations email similar to what dual lingo does
03:30 - 04:00 everything is automated for each user now coming back to the app you can now see a cool modern homepage showcasing a highlighted book with this 3d effect there are also some other latest books here there's this library page where you can see a full list of books available in the library with pation boort and you can easily search for any book or filter it through your liking and if you click on any of these books you'll go to its Details page where you can see complete information from Total and available copies description trailers summaries and more as well as a bunch of other
04:00 - 04:30 similar books you might want to check out but if you click on borrow book you'll see a destructive toast saying you can't borrow a book until your account gets approved Yep this is what we're doing we're not letting just anyone visit the website and try to borrow a book instead we'll allow admins to approve their account similar to what universities or other institutions do to verify your admission and to mimic that real situation we have an entire admin dashboard for managing everything regarding the content and the users you can visit the admin dashboard right
04:30 - 05:00 within the website by going to/ admin now of course if you're a normal user you'll automatically be redirected back to the homepage otherwise you'll access this whole new website that has a sidebar on the left and the header with the main page showing complete analytics like the total number of bored books users and more we're also comparing these analytics with the previous week so you know if there has been an increase or decrease in statistics and you can also see a list of recently borrowed books and newly joined users now a super useful thing here is that all of this information loads
05:00 - 05:30 independently without blocking any other part reload and you'll see different loaders and some information will soon show up followed by other pieces this is called performance optimization and even if an error occurs only that specific part is affected while the rest of the app stays functional and non-blocking after that we also have an all users page where you can see a complete list of users who joined and search or paginate them each user has its role and through the admin dashboard you'll also have the ability to make anyone an admin
05:30 - 06:00 there's also an all books page where the complete list of books is shown with search and pation so let me create a new book by providing lots of details including the title genre rating cover the color using the Color Picker and upload a video trailer let's first go to account requests where you can see a list of all user accounts that need to be approved let me see where mine is oh there we go now I'll click on it to approve it and confirm upon confirmation I'll also receive an email saying that my account has been approved now going
06:00 - 06:30 back to our main app if I click borrow book it's processing and there we go now I can actually borrow it which redirects me to my profile page see when I borrow this book something happened in the background if I go and check my email I'll see an email notification about the book being borrowed and showing me when I'm supposed to return it and then before the due date I'll also receive two different email reminders letting me know that the due date is coming and if I don't return it in time I'll also get penalty emails later on this workflow is
06:30 - 07:00 completely automated similar to what Netflix or other companies are doing giving you payment reminders and notifications pretty amazing and not something you can learn every day right back on the website you can see a kind of an identity card with all our information like full name University ID or university card and on the right side there is a list of books I've borrowed with the dates specifying when I borrow them and when I'm supposed to return them there's also a little receipt icon which when I click it will download the generated book receipt and save it to my my computer it's a NIC looking
07:00 - 07:30 customized receipt which I can then show to my university to get a book if I now go to the admin side specifically the borrow records page I can see my record alongside some other records listed here and admins can also see the receipts and confirm them and one thing here is that we can also search records by user and not just by the book pretty cool stuff and admins can also change the borrow book status so if somebody comes in and Returns the book in person we can change it so does it feel like a lot well if it does no worries because we'll break it step by step and if you're a beginner
07:30 - 08:00 still figuring out what components are then this course is not for you first check out my beginner crash courses from tailin CSS and react to next GS and then come back I'm so excited to Dive Right In but first let me tell you that this project completely took over my life what started as a simple tutorial turned into something huge I rebuilt it over and over again until it became a project that even senior developers would find impressive and unlike most tutorials I refused to rush through important
08:00 - 08:30 Concepts because you deserve better so here's the deal most of these courses on YouTube completely free I mean just see how long this video is free complete source code free figma designs everything but the tutorial got so massive I had to split it into two parts part one right here fully free part two on my soon to launch JS Mastery Pro platform with progress tracking real world challenges Advanced patterns get commits for each lesson personalized
08:30 - 09:00 learning paths and more other platforms would easily slap a $500 price tag on that but not me first 100 developers that click the link down in the description and join the wait list will get lifetime access for free and everybody else that joins will get a significant discount once it launches it's my way of saying thank you so click the link in the description and join the pre-launch spots can fill up pretty quickly so don't wait too long but even if you don't end up getting it no worries this video alone covers rain
09:00 - 09:30 limiting DDOS protection postgress databases and more all for free so with that in mind alongside the weightless link check out all of the free links as well to get the figma design the source code and so much more with that said let's Dive Right In and let me teach you how to build a set of these two apps a public facing one and a realworld admin dashboard to manage it super excited to get started developing this h huge application that covers features
09:30 - 10:00 and Technologies I've never before covered on JavaScript Mastery we'll first start by understanding the entire application and its structure how technically you will not only develop one application but two applications a user platform and an admin panel by using something known as a mono repo architecture yep we'll cover all of that in detail but first let's talk about the Technologies you'll use to build it of course we'll use the react framework for
10:00 - 10:30 the web next GS used by some of the world's largest companies enabling you to create highquality web applications with the power of react components so let's copy the installation command open up a new project in this case I'm using webstorm which is a powerful IDE which as of recently became completely free but of course you're free to proceed with any other code editor or IDE to follow along with this course open up your integrated terminal and paste the installation command and PX create next
10:30 - 11:00 app at latest It'll ask you whether you want to install the package that will allow us to create our app so press y for yes it's going to ask you what is your project name in case you've already created a folder within which you want to create this app which is what I did and called it University library then you can just press do slash to create it in the current repository it's asking us whether we would like to use typescript that's going to be a strong yes I'll later explain the many benefits you're getting by using typescript and and don't worry if you haven't used tab
11:00 - 11:30 script before you can proceed watching this video whenever I'm covering some tab script stuff I'll make sure to take my time and explain what's happening next it's asking us whether we want to use eslint that's another big yes we want our codebase to be scalable well structured and maintained we want to use tailin CSS as it's the preferred way of styling applications nowadays and we don't need a source directory it's just a matter of having an additional folder which we don't need of course we will be
11:30 - 12:00 using the app router so say yes turbo pack which is going to speed up our development time we can say yes for that as well and we don't necessarily need to customize the import alas so simply say no and as you can see next GS will now automatically install all of the dev dependencies needed for us to run our project very easily while it is doing that let me actually walk you through a couple of other technologies that we'll be using to develop this great application production ready scalable application are not built from scratch
12:00 - 12:30 trust me you don't want to reinvent the wheel which is why in this course I prepared some technologies which are industry standard open source and production ready for you to learn and build this application with as well as all of your upcoming applications first things first here is Tailwind CSS a very obvious choice for The Styling flavor of our applications it allows you to have full flexibility of how your applications are going to look like it's just like you're writing Reg CSS you
12:30 - 13:00 have all the properties at your disposal but you can use them in a quicker way by simply using a set of predefined utility classes and to make your application even more standardized we'll use shat nuui a library that allows you to copy some of the components but then use the full power of Tailwind CSS to style them to your liking for example you can take an alert or a button component and then make it completely your own of course buttons are simple but using shaten for something like forms where it comes
13:00 - 13:30 pre-built with react hook form and Zod which are the industry standard Technologies for managing form nowadays that's definitely going to come in handy I'm also happy to say that this is the first project on JavaScript Mastery that'll use a postgress database yep we're transitioning over to SQL and we'll use the world's most advanced open-source relational database but I won't make you deal with clunky SQL interfaces and outdated admin portals instead we'll use neon allowing you to
13:30 - 14:00 ship faster with postgress it allows you to use open- Source postgress databases with many features that make it such a breeze to use you get instant provisioning no waiting no config and it configures directly with your stack you can hook it up directly with NEX GS or to make it even more scalable and production ready you can use something known as an orm such as drizzle so yep not only will we use a postgress database will actually power it up but using an or an object relational mapping
14:00 - 14:30 tool which will allow you to make your database architecture that much more scalable and drizzle is definitely the way to go nowadays we'll also have to store a lot of assets like images and videos within our app and for that I've decided to use image kit it is a free tool allowing you to quickly optimize and transform your videos and images and just have a central Hub where you can efficiently manage all of the assets for your project more on that soon and we'll also use stach to make this application
14:30 - 15:00 truly production ready first we'll use appstash rdus a service that offers low latency data storage and retrieval perfect for caching session management and real-time applications we'll use it to teach you how you can quickly fetch books within our application but we won't just stop there we'll also use up Stash's workflows allowing you to manage and automate multi-step tasks great for things like onboarding or handling data processing workflow flows you can use it
15:00 - 15:30 to automatically send emails within your application for example when a user's account is verified by an admin or maybe check if the user is not active for the last 3 days or maybe check the user activity and if they haven't been active for the past 3 days send them an email or a notification similar to what dual lingo and most applications out there do in this case we'll also use it to send book borrowing deadlines limits and warnings so now that you learned a bit more about the text attack we'll use
15:30 - 16:00 let's head back to our editor and let's Dive Right into code now that our application has been set up before we go ahead and run it let's also set up shaten because we'll very quickly need it to create some of the components for the app and typically I would just give you the commands needed to install it but in this course I really wanted to guide you all the way through of how you would approach building something like this on your own so you don't just learn how to replicate this specific application but you'll
16:00 - 16:30 learn how to use the Technologies to build all of the apps in the future so head over to shaty and UI go to docs and installation for nextjs right there it's going to say if you're using next5 see next5 plus react 19 guide if you read through this page you'll notice that everything is still working as it should it's just that when you're installing some packages you will need to run an additional flag to install it because otherwise you'll be getting some warnings that's that's more or less it
16:30 - 17:00 so I'll head back to the previous page and just follow the installation setup with mpm I'll copy the First Command and paste it MPX shaten add latest in it press y to proceed and it's going to ask you for the style you would like to use in this case we'll use the New York style with the color of slate for the rest of the steps just press enter we'll use the CSS variables and now this is that warning that you get by using react 19 and next gs15 it
17:00 - 17:30 says that some packages May Fail due to a peer dependency issue so just say use Force I tried it a couple of times and trust me it won't pose any problems with that said we have just completed the second step of chassi and install as well and we finally can add some components into it such as a button we'll surely need to use a button right so whenever you're installing it you can just paste it right here MPX shaten add latest add button and is going to ask you whether you want to use Force once
17:30 - 18:00 again this is just an additional step you have to go through and press enter and there we go one component has been installed now let's go ahead and actually check it out if you head over to components UI you'll see that we have one additional component created for us by shaten now why I love shaten so much is that it actually asks you which components you want to install and it automatically gives you the code only for the components you want to use in this case the button and even though it might seem a bit
18:00 - 18:30 complicated you never have to dive into this code and modify it you're going to Simply use this code as a button that you created within your code base so if I head over to page. DSX which is our current starting application and if I delete everything within the homepage rather I just create an empty react fragment and within it I try to render this button coming from components UI button we can properly enclose it and I think we're ready to test it out I'll
18:30 - 19:00 open up the terminal and press mpm run Dev to run our application on Local Host 3000 once you open it up it should look something like this if you see this nice looking button that means that chaten has been installed now before we proceed with the rest of the theming I want to make sure that not only our app but our codebase remains clean as well and for that we'll use prettier and Es lent the setup for every editor will look a bit differently but you'll have to install the Plugin or an extension called eslint
19:00 - 19:30 as well as prettier and there's also an additional one called eslint prier which connects them together once you do that at least here on webstorm I can head over to preferences and I can turn on the manual preder configuration and try to find the preder package if it's not there yet we might need to install it so head over to your terminal and open up a new one because this one is currently running our application but on the second one we can install the additional depend dependencies and I'll say mpmi D-
19:30 - 20:00 save-dev as we want to install it as a Dev dependency and I'll say prier es L should have been automatically installed by nextjs so now I can head over to prier and head over to manual and just select it right here from node modules and run reformat on Save I want to do a similar thing for eslint where I can choose the eslint package from node modules and then find a configuration file and we
20:00 - 20:30 want to run es Lin fix on Save now if you make a change your code and press command s it'll actually try to reformat the code I know that the setup for other code editors might be a bit different so if you want to give webstorm a try which I have been really loving lately I'll put the link down in the description so you can get it completely for free and then follow along with my setup in any case you should be able to head over to es L.C config.js and we can extend some additional configurations such as next core web f titles next typescript as
20:30 - 21:00 well as standard we can also add plugin Tailwind CSS SL recommended as well as prier so all of these packages will work together to make our codebase clean oh and I think we can go over to command shift p prettier and then also turn on the runon save action which will actually apply all of the es length and prer changes on Save You Can See it'll add some space spaces in between these brackets as well as apply a semicolon at
21:00 - 21:30 the end among the other packages that I'm using which you can also install on any code editor there is the rainbow brackets which will make sure that you know where's the start or end of your function or code block I'm also using react Snippets so just make sure to install I think in this case I'm using modern react Snippets either way should be fine I know many of you ask me for the theme in this case I'm using a material theme material deep
21:30 - 22:00 ocean and that's it that's my editor hopefully you were able to set up Prett your and eslint as I did by further configuring this eslint config file but if not don't worry at all you can just proceed normally I know that sometimes setting up these linting tools can be a pain in the ass and it just messes with everything so even if you didn't manage to set them up properly don't worry at all we can proceed and while we're here I'll also turn this home into an S6 Arrow function by saying const home is
22:00 - 22:30 equal to and then finally at the end we can export default home and in case you have nothing right here at the top of the function you rather have just a single return we can also just turn this into an immediate return by removing the curly brace so you'll see that throughout this course I'll try to keep the code base as clean as possible so you can follow along see everything clearly and learn as many best practices is as possible now let's set up the
22:30 - 23:00 theme of our application in this case I decided to go with a very unique layout something that you don't see often nowadays so if you haven't yet downloaded this figma page definitely do that the link will be down in the description that'll make it much easier to follow along in any case you can see that some things here are shared such as this background image which we can export as well as some of the colors of the background or this primary yellowish color that we'll use so to make her styling in the future a bit easier we
23:00 - 23:30 can add all those to our Tailwind config so head over to Tailwind doc config.sys so head over below this lesson to a
23:30 - 24:00 GitHub repository containing the entire source code of this project go to its read me and head over to Snippets to copy there you can find this tailin config file in there we'll do some simple things like set up our primary default color as well as the primary color for the admin interface because this is a monor repo after all so we'll have to keep track of different themes and if you think about it I purposefully made the admin interface a bit different looking because for this application it'll most likely be opened on a desktop
24:00 - 24:30 computer somewhere in the library and it has to have a very simple to follow design so clerks or non-technical people using it can very easily create new books or give books to rent and this is not just when it comes to book managing whenever you have an admin interface that has to be put at some kind of a desktop computer for employees to use you always want to use a light simple to use interface now even though I provided these colors to you I want to teach you how you can extract them you can create tilin configs manually let's go with
24:30 - 25:00 this default primary color for example E7 C9 A5 how would you go about finding it so if you head over back to our figma file and go to O obviously this yellowish color is our primary color and right here under colors you can see it Styles you can copy it and then you can paste it right here in the code and I did the same thing for all of the other colors I didn't want you to manually go ahead and copy every single one so I decided to provide them here for you no logic here just some Styles the second
25:00 - 25:30 thing you'll need that'll make our styling a bit easier is going to be the global. CSS file so head over to global. CSS select everything and override it with the global. CSS file that you can find in the Snippets to copy for this video there's a lot of stuff that I'm providing right now at the start which is just to get us going but trust me as we dive deeper less and less almost nothing will be provided as we'll code all of the complicated logic IC together from scratch now as you can see in the
25:30 - 26:00 tail config we're using two custom font families so we have to go ahead and Implement them if you head over to app in there we'll be able to put local versions of our fonts this is a new nextjs feature allowing you to locally import the fonts without having to rely on thirdparty services so right below the Snippets to copy you can find a zipped fonts folder unzip it and then simply paste it right here within the app it'll look something like this fonts
26:00 - 26:30 with a couple of different variants once we have it you can head over to layout. DSX and we can put those fonts to use similarly to how a Guist font was used before instead we'll import local fonts this time so instead of Guist Sans we'll be using the IBM Plex Sans and we'll do it as a local font coming from import local font from next SL font SL looc to this local font you
26:30 - 27:00 have to provide a source pointing to that font so a source will be an array and here we can have a couple of different objects where first you specify a path to that font so that'll be/ fonts SL ibmx Sans make sure to capitalize the right things and then Dash regular.ttf we can also give it a font weight of
27:00 - 27:30 400 and finally a style of normal now we can duplicate this three more times for the second one we'll get the Sans medium and that'll be a weight of 500 with a font style of normal after that we can have a semi bold that's going to be 600 for weight and then finally we can have a bolt which is going to be 700 you can see how nicely prer structured this it figured out that
27:30 - 28:00 this line is extending over the normal character line limit so it put it into a new line in this case I actually don't think we need to say do slash for each font so I can basically remove that dot at the start and then I can manually set this to be a single line as well this is a bit easier to read and understand with that in mind we can also set up our second font by saying const beas new that's going to look something like this
28:00 - 28:30 make sure to have the spelling done right we're going to take in the local font and we're going to Simply point to a single object that'll have a source equal to an array where we have a single object of path pointing to for SL font SLB new- regular.ttf weight of 400 and style of normal we can also add an additional variable which we can use to
28:30 - 29:00 apply this font later on by calling it be- new perfect so now we have successfully set up the fonts and we can also set up the metadata of our application here by changing the title to bookwise as well as changing the description to something like bookwise is a book review website or in this case it's actually a book borrowing University Library management solution there we go that's more like it
29:00 - 29:30 now we have the metadata as well and we ready to use all of these within our application now sometimes nextjs starts with this export default function root layout we can simplify this a bit this readon is not necessary and we can just get the react node automatically from react by importing it so just import react node from react no need to get the react itself if you do that it'll actually put it to one line and and we can change it to an arrow function by saying const
29:30 - 30:00 root layout is equal to and then a callback function to the return statement like this this is a bit easier to understand in my opinion and we can also run the export default root layout finally we're no longer using the Guist Sans and the Guist mono rather we're using the IBM Plex Sans do class name so we'll actually be applying this font using class names and we're using the beas new font which will to apply through a variable great so this is our
30:00 - 30:30 root layout now that our setup is done we have successfully implemented the Tailwind config as well as done some es length and preder configuration I showed you which packages I use and more we have also applied the new global. CSS as well as imported all the fonts within our layout that means that we have laid some foundations for the start of the development of our project so in the next lesson let's start working on the homepage I think now is the perfect time to pull my browser side by side by my editor so
30:30 - 31:00 you can see the changes we're making live that'll look something like this you can see our button is automatically using this primary theme and right now we are on Local Host 3000 which is our homepage but some pages in The Design will use different layouts for example the odd page won't have the navigation bar whereas all the other pages will so let's go ahead and implement the layout that will allow us to reuse the navigation bar across all of our root routes I'll do that very quickly by
31:00 - 31:30 creating a new route group within the app folder by creating a new folder called root you have to wrap it in parentheses to make sure that it becomes a route group now what route group allows you to do is to create a new layout specifically for the pages within that route group so I'll create a new layout. TSX and I'll run rafc right within it to quickly spin up a new layout component
31:30 - 32:00 another thing I'll do is move this page right here from the root route to the root route group because this will be our primary homepage nothing will be changed after we make sure that this layout actually Returns the children route and we can do that by saying con layout accepts the children which are of a type react node so we're simply telling typescript hey we want to get some children through props here and they're of type react node once you do that we can return a main tag we use an
32:00 - 32:30 HTML 5 semantic main tag when you want to make sure that this is the main container so we can actually also give it a class name equal to root- container within it we'll also have another div which we'll use to set up our routes and the max width of our content which will display in within the main container so let's give it a class name of margin X of Auto as well as a Max W of 7 XL and within it we can render a header which
32:30 - 33:00 for now will just be a piece of text as well as a div with a class name equal to margin top of 20 to divide it from the header as well as a padding bottom of 20 and within it we can render the children meaning the root button you can see how now the max W that we applied is nicely centering the elements and even if we increase it they will always remain centered now what you can notice is that if you hover over a specific element hopefully it does the same thing
33:00 - 33:30 for you but it's actually pointing me to the style within a global. CSS file where you can see which styles are getting applied for example this utility class called root container is applying a Max H screen as well as some Flex properties with a background cover and for Native tailin CSS properties like margin top of 20 if you hover over it you can see what styles is it applying if you can not see that maybe you don't have a Tailwind CSS plugin installed or
33:30 - 34:00 an extension so go ahead and install it and it should hopefully work for you but if it doesn't no worries because most of these names are pretty self-explanatory anyway with that in mind we can now create our first component of the day by creating it right within the components folder and I'll create a new file called header dot DSX I'll run rafc within it and we can now nicely import it within the layout by simply saying
34:00 - 34:30 header coming from components header you'll notice the difference within Shaden components and our own components because Shaden components will be within the UI folder whereas our own components will be right here within the components folder and with that in mind you can see that now this layout is doing its thing and we are ready to implement the header which will show on top of the homepage and then implement the homepage itself so let's start with the header first I'll wrap it in an HTML 5 semantic
34:30 - 35:00 header tag that'll have a class name equal to margin y of 10 Flex justify Dash between as well as a gap of five within it I'll show a link component coming from next link and I'll give it an HRA pointing to forward slash meaning just the homepage within it for now I'll simply say bookwise right below this link I'll show a UL an unordered list that'll have
35:00 - 35:30 a class name equal to flex Flex - row items D Center and a gap of eight and within it we can render An Li which is a list item that'll render a link and this link will have an href pointing to for SL Library where we'll be able to explore all the books and it'll say library now we can also give it a class name equal to text-base cursor Das pointer to let it
35:30 - 36:00 know that it's clickable and capitalize and after that we want to check on which route we're on so we know whether to make it currently selected or not this is what I'm talking about if the search is selected we have to change it to a primary color so we know we're on that page right now to be able to do that we have to know on which path we're on and it's so easy to do that in xjs by saying const path name is equal to use path name coming from next navigation now as
36:00 - 36:30 soon as you use a hook within your component you have to turn it into a client render component by adding a used client directive at the top now we can change Styles dynamically by copying all the Styles we have here right now and using a CN class name property coming from lib utils which was created for us by I believe shaten maybe it's tailwind and in there as the first parameter you can pass all of the Styles which you always want to pass but then you can also pass a condition something like if
36:30 - 37:00 path name is triple equal to library or forward SL library in that case you can also apply a text light of 200 else you can apply a text light of 100 so now you can see Library selected and it's glowing yellow it looks a bit weird because we have a white background but don't worry we'll deal with that soon with that in mind here for the bookwise I actually want to use a logo so what we need to do is manually import it from
37:00 - 37:30 figma you can do that by double clicking on it and exporting it there's going to be many different small icons we'll use throughout this course such as this magnifying glass icon this book borrow request and more so to make your life of grabbing all of these individually a bit easier I went ahead and did that for you so you can find a zipped folder containing all of the assets right where you found the other code Parts delete the current public folder and then simply unzip and paste the new one it
37:30 - 38:00 should look something like this having our favicon as well as some icons and images if you now go back and reload it should look good you can see that we have one error and this error is a hydration error now this hydration error in nextjs is an error that is hard to debug because in most cases it's not even your fault that it happens basically what nextjs is saying is that your browser changed the version of the HTML before react was loaded which then
38:00 - 38:30 means there's an HTML mismatch on the client side and this can happen because of the some things you're doing in the code but more often unfortunately it happens if the client has a browser extension which messes with the HTML before react was loaded so you'll have to manually disable a couple of your extensions until you figure out which one it is for me it was grammarly so I just disabled it and now the error is gone with that said now that we have this dark background we can
38:30 - 39:00 no longer see this bookwise text so let's actually render an image we can do that by using a nextjs image tag coming from next image pass it a source of for SL ions SL logo.svg with an ALT tag of logo as well as a width of 40 and a height of 40 as well so that it look something like this perfect and for now
39:00 - 39:30 that means that our header is good so we are ready to start implementing the homepage our homepage will actually be super simple it'll consist of two major components one of which is the hero section or the book overview so at all times at the top we'll be displaying an overview of a new book like this origin book by Dan Brown so on the left side we'll display some text and information about the book as well as a button to board borrow it and on the right side will'll display its image and then below
39:30 - 40:00 we'll have a list of popular or latest books so that means that it will actually consist of two separate components so let's create them the first component will be called book overview. TSX you can create it within the components folder run rafc and that's it and then we can create the second one which will be called booklist TSX you can also run RFC in there now
40:00 - 40:30 what we can do is import and render both of these within the home so I'll have a simple react fragment to wrap both of them because we cannot just return two children without wrapping them first and then I can render the book overview as well as a book list and make sure to import both of them from where they need to come once you've done that let's head over to book overview and let's turn it into a section with a class name equal to book overview then within it we can
40:30 - 41:00 render a div with a class name equal to flex Flex D1 flex-all so the elements appear one below another and a gap of five and within it we can render an H1 that'll render some kind of a title so this will be a long book title and that'll look something like this on mobile devices so now we at least know that the book overview is being rendered but what about the book list well let's
41:00 - 41:30 head into it and this book list will also be a section within which we can render an H2 that'll say something like popular books and we can give it a class name of font db- new text- for Excel and text- light 100 there we go so now we have something like this and now is the time to turn it into something that looks more like this where we get an actual book title book
41:30 - 42:00 information and the description as well as its cover and then we can also show some more covers at the bottom let's do that in the next lesson to get started with showing some data on our homepage so we can quickly build out the UI before we focus on other functionalities let's first import some sample data or dummy data if you will this will allow us to focus on the UI and later on exchange it for real data which which will fetch from our optimized database to do that you can
42:00 - 42:30 head over to your application and create a new folder called constants within the constants create a new file called index.ts and here we can create some of that fake data that'll look something like this export const sample books is equal to an array of objects of all the information about different books such as an ID of one than a title something like the midnight book and so on but a
42:30 - 43:00 book has many more pieces of data so for that reason I provided a couple of sample books in the Snippets to copy this contains no programming logic just some static data so copy it and paste it right here into constants you'll notice that here we have some navigation links that we can later on map over as well as a list of many different sample books with their titles authors genre descriptions colors covers videos summaries and more so now we can take
43:00 - 43:30 those sample books and pass them into the book list by saying title will be equal to latest books books will be equal to sample books coming from constants and we can pass some kind of a container class name of margin top of 28 to divide it a bit from the top and under the book overview we will simply render more information about the current book by simply
43:30 - 44:00 spreading all the information of sample books zero so that we can head into the book overview and accept all the information about a book right through the prompts we can destructure them such as a title author genre rating total undor copies available uncore copies yep will implement the entire inventory system so users always know how many books are available we can also add a description color and cover as well and
44:00 - 44:30 now right here just to verify it's working under H1 we can render a book title The Midnight Library there we go now you can see that our tab script is complaining a lot here saying that binding element author has a type of any what we can do instead is create some generic types and to create them we can actually copy all of these props that we have here and then create a new file in the root of our application
44:30 - 45:00 called types. D.S in there you can create a new interface called book and you can paste all these props that you just copied select multiple lines as we can edit them remove the comma and instead make them all to be equal to a string like this now not all are going to be strings some are going to be numbers like an ID property which will be set to number title is a string author is a string
45:00 - 45:30 genre is a string rating will be a number total copies will be a number available copies will be a number as well next we're going to have a description color cover and we're also going to have a video of a type string summary of a type string as well as an optional is loaned book which is going to be a Boolean so with this we're now telling t script how our book should look like and you can Define it by simply saying colon book so
45:30 - 46:00 these props are of a type book great now what do you say that we render some more information about this book right below the H1 I'll create another div with a class name equal to book- info and within it I'll render the first P tag that'll say bu and then a span and then within the span we can render the author's name that'll look something like this but of course we can make the span a bit nicer looking by giving it a class name
46:00 - 46:30 of font D semibold and text dl-200 there we go that's going to give it that little pop we can now duplicate this P tag one more time and for the second time it'll say category and then we can set the category to be equal to genre either word is fine there we go and this is looking good to me below this P tag we can have another div that'll have a class name equal to flex and flex D row with a gap of one within
46:30 - 47:00 that div we'll render an image coming from next image with a source of SL ions slst start. SVG with an Al tag of star width of about 22 and a height of 22 and right below it we can render a P tag that'll say rating so now we're displaying the rating of 4.6 for this book as well now let's hit a bit down so one and then two divs down I'll create
47:00 - 47:30 another div that'll have a class name equal to book- copies and within it we can render a P tag that'll say total books and then we can render a span which will render the number of total copies of those books and we can do another P tag Below in a similar way that'll say available books which will render the number of available copies so
47:30 - 48:00 we have total books 20 available books 10 we can now go below this div and create another P tag for the description by giving it a class name is equal to description or book description and we can render the description right within it like so there we go a dazzling novel about all the choices that go into a life well lived interesting I mean you could have some fantasy or fiction books here as
48:00 - 48:30 well but as you know we're going to turn this into University Library later on below the P tag we can render a button this is a button coming from our chatan components with a class name equal to book- overview BTN and within it we can render an image with a source of SL ions SL book.svg with an Al tag of book a width of about 20 and a height of about 20 as well
48:30 - 49:00 right below the icon we can render a piece of text that'll say borrow book and we can give it a class name of font db- new text- excl and text- dark 100 I think just borrow should do because it makes sense we want to borrow the book finally we can render the book cover right below or to the right side on desktop devices so let's go below load this div and create another div with a
49:00 - 49:30 class name of relative Flex Flex -1 and justify Dash Center within it we can have another div with a class name off relative within which we can render a new component called book cover so let's head over to the components folder and create a new component called book cover. TSX run rafc and now we can import it right here into the book overview by saying book cover and to it we can
49:30 - 50:00 pass a couple of props we can first pass a variant will this be a wide or not wide we can then pass a class name should it appear above or below other elements in this case like a z index of 10 so it appears above we can pass a cover color equal to color as well as a cover image equal to cover of course this is the UI we're trying to implement so we'll have two different covers one
50:00 - 50:30 above and one below so right below this one let's create another div which we can absolutely position a bit away from the original one by giving it a position of absolute left of 16 top of 10 rotate of 12 opacity of 40 and a Max small devices hidden because we don't have enough space to show it and within here we can also render the book cover it'll be very similar but in this case it
50:30 - 51:00 won't have any additional class names because we want it to appear below the top one so now we can dive into the book cover and implement it within here we want to accept all the props we've passed into this component so we can do that right here at the top and say we'll be accepting a class name let's also get a variant of the book cover a cover color as well as a cover URL and and we can define those as props so now right
51:00 - 51:30 here at the top we can create a new interface of props we can Define the class name which will be an optional and a type of string we can define a variant which actually will make a bit more complex by creating a new object of different variant Styles such as con variant Styles is equal to an object where we have extra small which will correspond to the class name of book- cover underscore extra underscore
51:30 - 52:00 small now we can repeat this over for small medium regular and wide so let's rename them appropriately we have small which is going to be book cover underscore small next we have medium so we can rename this one to medium and book cover medium after that we can have regular which is going to be book cover regular and and finally we'll have wide which is going to be book cover wide
52:00 - 52:30 there we go and just to make sure tab script is safe we can Define the type of this as record of book cover variant so that'll look something like this book cover variant and then a string for the second part of it so what is this book cover variant well it'll simply be another tab script type so we can Define it by by saying book cover variant and it'll be one of these few different
52:30 - 53:00 things so let's call it variant variant here as well and it'll either be extra small or small or medium or regular or Y so now we always know that we can use one of these variant Styles and within our interface we can Define that the variant will actually be of a type book cover variant after that we have a cover color and a cover URL the variant will be optional because we can Define some
53:00 - 53:30 default book cover variant with that said let's provide those default values for example the default variant can be regular if we don't pass any other one through props the default cover color can be something like hash 012 b48 and then a default cover URL can be a placeholder photo like https Colin SL placehold Co 400 over 600. BNG this is
53:30 - 54:00 just some placeholder image great and now we can put those props to use so instead of simply saying book cover right here let's actually return a div with a class name equal to first I'll wrap it into CN which stands for class names and pass in the classes that it'll always have such as relative transition Dall and duration of 300 so it will have a slight animation after that we can pass the
54:00 - 54:30 variant Styles and then get into a specific variant for passing through props so what this means is that if a variant is regular it'll go into the variant Styles and apply a variant book cover regular class name and finally we can provide additional class names that we pass through props after that within this div we can render a book cover SVG so let's write something like book side SVG which later on will turn into a new
54:30 - 55:00 component and then below it we can render another div this div will have a class name of absolute and a zindex of 10 to appear on top of other elements it'll also have some additional Styles which we will write in line such as left will be 12% the width will be set to 87.5% I found that value to work the best and of course we have to put it within a string and then after that
55:00 - 55:30 we'll have the height which we can put to about 88% here we go and now within this div we can render a xgs image so let's just say image right here with a source of cover URL so the one we're passing through props and an I'll tag of something like book cover and close it right here this image also needs to have a fill property as we wanted to fill the entire space of the
55:30 - 56:00 div a class name equal to rounded DSM as well as object dill and I think that should be good but as soon as I try to actually showcase that image it looks like it picked up a placeholder image so it wasn't able to show it right here because it's not added to the next config so we have to head over to next config JS and add place told that Co right there so let's do that right now by heading over to next config and here
56:00 - 56:30 you'll have to define the images object which will contain a remote patterns array which will contain different places from where you want to get access to images the first one will be a protocol of https and it'll have a host name of the one you just copied placehold doco later on we'll be able to add some other places from which we might want to get our images for now we have the placeholder so if I reload you
56:30 - 57:00 should be able to see our placeholder image but wait why are we getting a placeholder image and not this image we're passing right here well that's because here I called it cover image and back in the book cover I called it cover URL so let's actually change this over to cover image so it appears nicely and then we can also refer to it right here as cover image now you'll see the host name will change because now it's picking up the image from our constants index. DSX or DS where the image is
57:00 - 57:30 coming from Amazon so let's actually head over to our next config and now you know what we need to do we have to add another remote pattern with a protocol of https as well as a host name equal to and we can copy it from here from the error message m.m media amazon.com it'll look something like this so if you do that and reload you should be able to
57:30 - 58:00 see a great looking cover photo but we won't stop there we'll actually turn it into this very interesting 3D look where we'll wrap it with some kind of an SVG container and then put another version of that image right behind it for this more complete look so to achieve that let's render a new component by heading over to our components folder and creating it and I'll call it book cover SVG do DSX this is going to
58:00 - 58:30 be just the regular SVG image so think of this as an image file you cannot really code this out so I'll provide it to you alongside the other files simply find the book cover SVG copy it and paste it here as you can see these are the different paths that create this SVG image the reason why we had to have it as a separate component is so we can pass the cover color of the image and match it with the rest rest of the book so now let's head back over to book cover and let's render this book cover
58:30 - 59:00 SVG and pass the cover color of cover color right into it and you can see that now even in Mobile we have this more completed look there's one thing we have to fix and that is that in the interface props we're still calling it a cover URL but here it's called a cover image so let's actually fix the name right here in the interface of props to be cover image as well just so our book overview doesn't complain now let's check it out on
59:00 - 59:30 desktop you can now see that the Midnight Library is appearing we have two books so it appears like one is stack on top of the other and we also have this nice looking SVG which is in the color of the cover pretty cool if I may say so myself now with that in mind the hero section is now done so the next thing we have to do will be the popular or the latest book section right below the hero for that we can close the overview and the cover and we can go back over to page. DSX and head into the
59:30 - 60:00 book list to accept all of the props and start implementing it so let's immediately access the props we're passing from the Page by these structuring the title the books as well as the additional container class name that'll look something like this and we can set it to be of a type which is called props and we can Define that interface right here above by saying interface
60:00 - 60:30 props and it'll contain a title of a type string a books property which will be of a type book array remember we already created that Global typescript interface right here and then finally we'll have a container class name which will be optional of a typ string so now let's put those to use by giving this section A Class name equal to container class name below it we have an H2
60:30 - 61:00 that'll simply render the title so that'll look something like this it'll say latest books and finally we can render a UL an unordered list right here below and it'll have a class name equal to book list and right here within it we can map over those books by saying books. map we can map over each book and for each book we can render a new component so let's
61:00 - 61:30 actually head over to the components folder and create a new component called bookc card. TSX run rce right within it and then we can just import it and render it right here by calling the book card for each one of the books we map over of course since we're mapping over it we also have to provide it a key and a key can be equal to book. tile because in this case all of our books are going to have distinct titles and then we can
61:30 - 62:00 also spread the rest of the properties of the book so we can very quickly use them within the book card oh looks like I misspelled this container class name right here see how useful it is to have typescript because it's telling us that it doesn't exist on the props above so I will just fix it right here and now it'll get moved a bit down so with that said let's move over into the book card component and let's implement it I think you can start seeing how we're moving from pages to then smaller pieces of UI
62:00 - 62:30 that we can then reuse across different pages also this borrow button is looking a bit weird to me so if I head over to book overview let's check it out it is right here oh I think I misspelled overview so if I fix overview right here yep now it has a bit more weight to it and it's also looking a bit better on desktop devices perfect so let's go back to the book card and let's implement it first we can accept all of the props such as the ID
62:30 - 63:00 title genre color cover and is loaned book which can be set to false for now by default and those will be of a type book how useful it is to just be able to reuse the book type now that we have that let's actually turn this return statement into an Li and what you can do is if you just have a return statement and nothing else you can just immediately return it without specifying
63:00 - 63:30 the return statement so that'll look something like this within this Li I will render a link component because each of these cards will actually be clickable and It'll point to forward SL books forward slash and then the ID of that specific book that'll look something like this and we can close that link right here and within it we can try to render the book cover see how cool this is we will reuse the book cover component that we created before by passing a cover
63:30 - 64:00 color equal to color as well as passing a cover image equal to just cover let's not forget to import this book cover from the top and would you look at that immediately we get back all the dummy books that I created before so this is already looking so much better I think typescript is letting us know that we're missing the variant prop in the book so let's see how we have defined it we said if no variant is passed it'll equal to default so since we have the default
64:00 - 64:30 value I think we can make this variant optional and in that case it'll be good because it'll just take the default variant this is pretty useful to be able to use the default values for props in situations where the most common type of prop you will pass will be the one that you set to the default value but when it changes you can then easily override it by passing some kind of a different variant like wide now let's give this Li a class name I will set it to be equal to I'll
64:30 - 65:00 do a CN property and then I'll check if is loaned book and only if it's loaned then on extra small devices we'll do a w of 52 and W of FO you'll see what this will do soon to this link we can also pass a class name we'll do the same thing CN and then if is loan book we will give it a w- fo a flex property flex-all and items D center now let's go
65:00 - 65:30 below the book cover and let's render a div that'll render two different P tags the first one will have a class name of book- title and you can guess it it'll render the title and the second one will have a P tag of book genre so that'll look something like this and right within it we can render the genre okay this is looking a bit better let's also style this div by giving it a class name of CN it'll
65:30 - 66:00 always have a margin top of four but then only if it is not loan book in that case we can give it an extra small of Max W 40 and typically a Max W of 28 so in this case since they're not loaned they will all be able to fit in this nice layout but now if it is loaned we can go below this div and say if it is loan book in that case we can render another div with a class name of margin
66:00 - 66:30 top of three with a W of fool and we can render another div with a class name of book- loaned and within it we can render an image coming from next image with a source of SL ions SL calendar. SVG with an Al tag of calendar a width of about 18 a height of 18 and a class
66:30 - 67:00 name of object Das contain right below it we can also put a P tag that will say something like 11 days left to return and we can give it a class name equal to text- light 100 oh and let's put this P tag right in this div and then we can exit one div and render a button and that button will simply have a class name equal to book BTN and we
67:00 - 67:30 can say download receipt there we go so right now we don't have any loan books but I think if we head over to constants which is right here index. TSX and add the is loaned book property and set it to true you can now see that this will take more space and it'll say download receipt so as we actually expand this you can see how that will look like there we go so here it looks
67:30 - 68:00 similar because we have more space to show all the data but on mobile devices it actually takes the full row so we can properly show everything for now I will remove that is loan book property from these fake dummy books believe it or not we have completed our book card it was a pretty simple component so now it's looking something like this we can now close the book card the book list where the book card is used as well the book covered and with it we have implemented the two components that our homepage is
68:00 - 68:30 made up of so let's open it up in the full screen there we go we have a nice looking hero section with this SVG book cover and then we have some latest books right here falling nicely into line with that in mind our homepage UI is now complete so we are ready to move to pages that users need to visit before they can visit the homepage or borrow any books and that is of course the o we have two beautifully designed o pages with the form on the left and then the
68:30 - 69:00 cover image on the right same thing right here for the signin it has less Fields but the same structure so in the next lesson I'll teach you how we can do those two in a way that we can reach full reusability of all of these forms and components that make them great work on completing the homepage to get started creating our authentication Pages let's first create the routes we can do that by heading over to our app folder and creating a new route
69:00 - 69:30 group that's going to be a folder which I'll call O and make sure to wrap it in parentheses to make it an O group within it I'll create a new layout which will only serve the odd group routes this time we won't have the nav bar so let's create it layout. TSX run RFC and as you know with every layout it must get children as props and children are of a
69:30 - 70:00 type react node and then it simply can return that children for now so now whatever other Pages we create it will simply show those pages let me show you I'll create two new folders within o one will be called sign- in and the other one will be called sign Dash up within both of these we'll create a new page. TSX to create a route within which
70:00 - 70:30 I will run rafc so in both of these Pages just create a new page. TSX and run RFC if you do that we can now manually move over to both of these Pages by heading to forward slash sign- in or just sign Dash up there we go so let's first focus on the layout as it is a bit Bland and both of these pages will share it so I'll move over to layout belonging
70:30 - 71:00 to O first things first I'll wrap everything in an HTML 5 semantic main tag with a class name equal to o dash container for now I will remove the children within it we'll have another section which we'll use for the form so it'll have a class name equal to Au Dash form there we go so now we get that nice looking background we'll also render a
71:00 - 71:30 div with a class name equal to off-box so this is the box for the form and then within it we can render an image belonging to next image with a source of SL iicons SL logo.svg with an ALT tag of logo a width of about 37 and a height of about 37 as well if we close it we get this nice looking logo and we can render an H1
71:30 - 72:00 that'll simply say bookwise which is the name of our application with a class name equal to text- toxel with a font semi bold and text- white there we go let's also wrap those two in an additional div so both the image and the H1 that'll have a class name equal to flex Flex D row and the gap of two just to to make them look a bit nicer now we can go below that div and we can render another div that will render the
72:00 - 72:30 children meaning whatever is appearing on both of these pages so now if I change this text on the signin page to signin form I think you'll start getting the idea of where we're going and I'll change it to sign up form here so now if I manually move to sign in you can see that even though the layout Remains the Same here we have the signin form and here we have the sign up form and finally both of these will actually share the right side of the screen as
72:30 - 73:00 well so I can create another section which will have a class name equal to O illustration and within it we can render an image that will have a source of SL images SL off- illustration.png with an Al tag of O illustration with a height of about 1,000 a width of about 1,000 as well and
73:00 - 73:30 finally a class name equal to size- full and object Das cover so you can see on mobile devices it's going to show up at the top and on desktop devices it'll take half the screen because we don't really need much more for a simple form how do we make that happen well if you look into the O illustration you'll notice that we're setting it to Sticky with the height of 40 W fo so it takes the full width and then in small devices we're positioning
73:30 - 74:00 it at the top zero and in small devices we're making it take the full screen so it's going to Simply depend on the screen size of how it's going to behave as soon as we go over the small it'll take the full height and then expand and an extra small it'll look like this with that in mind we're now done with the layout and we are ready to start implementing both of these pages but we won't just create a form here and then just copy it over and do do the same thing here with a couple of different inputs not at all we'll create a single
74:00 - 74:30 o form component that will'll be able to then switch between when we move from sign in to sign up let me show you how we can do that I'll head over to components and create a new component called o form right here. TSX run rafc within it and now we have the OD form now this OD form will accept a couple of different props such as the type of the form is it sign in or sign
74:30 - 75:00 up it can also accept something known as a schema meaning schema validation for the fields within that specific type of the form as well as the default values for the fields within the form and finally it'll accept a different onsubmit function because depending on which one we're submitting we want to either log the user in or create a new user so these will be of a type type props so let's define them right here at the top by saying interface props and
75:00 - 75:30 here for the first time we're dealing with a bit more complex typescript values where we're going to have a generic parameter of T what t means is that it's generic so it can take in the type of whatever we pass into it because we don't yet know how the exact structure will look like rather we'll take in the fields or the inputs that we're going to have by extending the field field values so we can say extend field values and this will come in from
75:30 - 76:00 react hook form but we cannot use this yet because we haven't yet installed a shaten form component that contains the Imports for react hook form as well as Zod so head over to shaten and search for form and it'll tell you here that forms are tricky one of the most common things you'll build in a web application but also one of the most complex in this guide we'll take a look at building forms with react hook form and Zod so let's actually go through the installation process we first have to
76:00 - 76:30 add it by copying this command and then pasting it into the terminal we'll have to press enter to force install it since we're using newer versions of nextjs and react and it'll install it into our components folder after that we are ready to create a schema the schema defines the shape of our form so let's actually head over to our lib folder and within it create a new file called validation dots within there we can do something like this export const sign up schema is
76:30 - 77:00 equal to Z doob and then we can pass all the fields that we want to have Z we have to import like this by saying import Z from Zod and this is coming with the installation of a component it's also asking us whether we want to override the button let's say sure yeah and now it'll update it and the form will be installed so which types of fields will we have on our signup form
77:00 - 77:30 well we'll have a full name so let's say full name and actually I won't use camel case here because later on once we store it in the database we have to keep it all lowercase like this and that'll be a z. string do Min characters of three so a string of a minimum of three characters after that we can have an email which will be a z. string email next we can have a University ID and you know what
77:30 - 78:00 typically we would have to have it as all lowercase or maybe something like an underscore in between but this is not really a standard JavaScript practice even though it is a good SQL practice and even my webstorm is telling me right here that there is a typo here this should have been full name so let's actually keep it camel case like this for our front end code and then when we pass it over to our back back end we can then switch it over to snake case with underscores but for now I'll leave it
78:00 - 78:30 like this University ID that'll have a z. coer number so it'll actually take a string and turn it into a number we do it like that then we're going to have a university card which will be of a type z. string do nonempty and we'll say University card is required perfect and finally we of course need a password so let's put a password Here of
78:30 - 79:00 a type z. string. M length of eight characters and let's define the schema for the signin by saying export const sign in schema is z. object where we get an email and a password and that is it and now we can use those two schemas as we pass them into corresponding components so let's head back over to our odd form and now we're accepting those different types of props so what I wanted to show you is how we can actually call this odd form depending on
79:00 - 79:30 the type from the two different pages that'll look something like this I'll say Au form Das Dash and then I'll render the type which we dynamically pass as a prop and then in both of these components the only thing I will render is a self-closing OD form component so that'll look something like this OD form to which we can now pass a type equal to signore n we can also pass a schema coming from validations equal to
79:30 - 80:00 sign in schema which will import it from validations as well as default values equal to email and password both at the start equal to an empty string and now if we save it and head over to sign in you can see that it says off form sign in and it has the sign-in schema so it knows which Fields it has it has the type as well as the the default values finally it also needs an onsubmit function which for now I will leave as
80:00 - 80:30 an empty callback function and later on we can Implement its logic so let me actually copy this entire code and paste it to the sign up form as well but this time I will change the type to sign up I'll change the schema to sign up schema so we have to get it from validations and under default values we'll have to pass a few more values like email password as well as full name which will start as an empty string
80:30 - 81:00 University ID which will start as an empty string and a university card which will also start as an empty string great and now you can see as we're switching between those both of these will be the odd form but their logic and the fields will show will change depending on the type now we can turn this component into a use client component right here at the top because it is a form and we're managing a lot of States here so it has to be rendered on the client side then
81:00 - 81:30 we have to define the form by importing a few things such as Zod resolver as well as use form from react hook form that'll look like this and we can Define the form as well as the onsubmit function so let's put that right here at the top of our component that'll be con form is equal to use form Z coming from Zod so you have to import it that'll be form and then we can Define the type of that form to be use form return coming
81:30 - 82:00 from react hook form to which we can pass the T generic type and then I don't think we actually have to have this type because we're defining it in a different way we can just call the Ed form and then Define the Zod resolver to which we can pass our Dynamic schema so it's no longer just a singular form schema it is the schema coming from params right here so it'll depend sometimes it'll be signed in and sometimes it'll be signed up and the same thing happens for
82:00 - 82:30 default values we won't just Define them here manually we'll say default values is equal to default values as default values of a type T coming through props and then we have a submit Handler which we can call const we can rename it to handle submit which will be of a type submit Handler that accepts the type t and it'll be equal to a callback function that is asynchronous and simply
82:30 - 83:00 takes in the data so I will rename these values in the type to data and just make it look like this so now we have a clean slate to work with which will work for both forms of course we have to define the props so let's do that right now interface props will take in that t that extends the field values which are coming from react hook form and it'll have a schema of a type Zod type which will take in that generic T parameter the default values which will
83:00 - 83:30 be of a type T so I think now we can understand what the t is T is actually the default values that we're passing into the function and this Zod type also has to be imported from Zod onsubmit will be equal to a callback function where we get the data of a type T which returns a promise that will result in a success Boolean as well as a potential error which is optional of a type string if it fails and finally a type which will be either
83:30 - 84:00 signore in or signore up so now we're setting our form up for Success because we're telling it that it can be one of those two types and we're defining all of the props we're passing into it to make it reusable without any problems these props will have to accept the dynamic T generic parameter and when we Define find the odd form we also have to specify it here that t extends field
84:00 - 84:30 values it should be done like this so now prettyer nicely put this into multiple lines so it's easier to understand we first have this interface where we Define the types of props that we accept into these function and saying that we're also passing a t generic parameter that extends the field values we have a schema default values onsubmit and type submit and then we Define it and now if you hover over each one of these things you know exactly what it is not only that you know what it is type
84:30 - 85:00 script as well as Zod and react hook form and everyone knows what each one of these things are which will make her debugging and turning this form to be reusable super simple let's move over to the Second Step which is to build out our form by importing the necessary components for it such as the input and the button we can do that right here at the top and then we can use use it within the form right here by copying the form and putting it as the return
85:00 - 85:30 statement that should look something like this if we save this we're done and we should have something that looks like this but instead we have something that looks more like this don't worry it's just an error saying that we haven't yet installed the input component from shaten so let's do that by saying MPX shaten add input you can see how simple it is now you don't even have to visit the docs you just say the component name and you add it there we go the input got added but we got another error this one
85:30 - 86:00 is saying that only plain objects and a few built-ins can be passed to client components from server components but if you check out some of our Pages like the signin page we're actually passing some more complicated objects as well as functions so that is not allowed which means that we'll have to turn these two components into use client components as well so just add the use client directive to the top of the sign in page as well as to the top of the sign up page so let's head over there and let's
86:00 - 86:30 add the used client when you do that you'll notice that it will no longer complain and we'll be able to see our very simple form right now so now let's implement the rest of the form now that we have the structure and the skeleton for what this form will become we can head to the form and add some information that goes around the form letting us know whether we're on the signin or signup form now you will start seeing how reusable it can get I'll do that by first wrapping this form in a div and then I'll pull it
86:30 - 87:00 within that div next let's give that div a class name equal to flex Flex Dash call and a gap of four and then within it create a new H1 that'll have a class name equal to text- 2XL font D semi bold and text- white and and within it we can check if is sign in is true which is one of the props for passing in that case we can
87:00 - 87:30 say welcome back to bookwise but if it's not sign in we can say create your library account something like that and of course this is sign in is actually a new field which we can create based on the prop of the type so right here at the top I can say something like const is sign in and that'll be true if the type is triple equal to sign in so now
87:30 - 88:00 it says create your library account and of course on signin it'll say something else we can do another piece of text below this AG one may be a P tag with a class name equal to text- light 100 and there we can say if is sign in then we can render a piece of text of well let's try to find it from the design something like let's see
88:00 - 88:30 access the vast collection of resources and stay updated and then if it's create we can do something like please complete all fields and upload a valid University ID to gain access to the library there we go so now if I get back you can see both the heading and the subheading right here we can do something similar below the form okay so here we have the form and then below we can have a P tag with a class name equal to text- Center
88:30 - 89:00 text-base and font Das medium where we can check if is sign in in that case we can say something like new to bookwise else we'll say already have an account we can also render a link that'll say a similar thing if is sign in it'll say create an account else it'll say sign in and of course we have to give this href a link so that'll be something something like this if is sign in then we can point it to for/
89:00 - 89:30 signup else we'll point it to slash sign in and of course make sure to import the link from next link let's also give a class name to this link equal to font Das bold and text- primary and let's put a space after both of these two texts so we can nicely see them so currently we are on the Creator account and it's saying if you already have an account head over to to sign in and if we're welcome back to bookwise if you're new create an account and now we have linked
89:30 - 90:00 in between those two forms which share the same layout as you can see but later on based on these we can change the inner content of the form so with that in mind let's create those different form Fields first we have a form where we call a handle submit and then we can pass in a handle submit because we rename that function we can give it a space- y of six as well as a w fo so it takes more space and then this is super interesting we will make this so reusable that you will actually doubt
90:00 - 90:30 the way it's working let me show you instead of just manually duplicating different form Fields like this which would leave us with having hundreds of lines of code and then on top of that you would also have to add checks like if is sign up then actually include this form and so on no we won't do that we will simply utilize the fields that we have above the default values So based on those default values we can show different form Fields I will first wrap
90:30 - 91:00 everything in a dynamic block of code and then say object. keys of default values so I just want to grab their names and the map over them where I'm going to get each individual field and for each field I want to return a what well a form field so now I will put this a bit up within the map function and you can see this immediately created as many fields as many we have added in our validation so
91:00 - 91:30 if you head over to validations dots you can see that for the sign up we have 1 2 3 4 5 and here we have just two so if you switch over between them you can see that's already working but definitely not all of them are just supposed to say username right so let's now make them unique first we need to pass a key to each one of these because we're mapping over them and the key will be fielded the name will be equal to field and just to make typescript happy we can say as
91:30 - 92:00 path of T and then we are rendering that field where we have a form item we have a form label to which I'll pass a class name of capitalize and I will select the right form field by accessing the field names coming from index.ts of constants and then we'll get a field name for that specific field by saying field. name and that'll look something like this now we can see that it says email password Here We have nothing for some reason
92:00 - 92:30 University ID number and upload University ID card so it's pulling them from our constants where we have defined what each one of these fields should say and make sure to rename full name to use camel case there we go you can do the same thing for the field types below now just to make tab script safe one more time we can say field name will be as key off type off field names like this so basically with
92:30 - 93:00 all of this what we're doing is we're saying that these form labels will belong to one of these things right here after the label we have the form control and here we can choose whether we want to display it as an input or as something else most of them will be inputs besides for the University card which will be an image upload so that'll be a new component which we have to create so let's create it in the components folder I'll call it image upload. TSX run rafc and now back in the odd form we can
93:00 - 93:30 check if field. name is triple equal to University card in that case we want to render the image upload component else we want to render a regular input component so that'll look something like this and of course this was supposed to be a string we're checking for the field name of University card so now you can see that everything is the same but here
93:30 - 94:00 it says upload University card that'll be an image upload which we'll Implement soon let's also style our input to make it required and we can also remove the placeholder and instead give it a type which will be equal to this part right here where we are mapping over the field names so I will copy that and paste it here but instead of of mapping over the field names we'll map over the field types like this and then say field. name
94:00 - 94:30 as key off field types right here and of course we have to import the field types coming from constants those are these right here which tell us should they be text email number or password if we structure it properly it should look something like this and then we spread all of the other field properties and render the class name equal to form input and that'll make them look just a tiny bit nicer like this and now check this out this one
94:30 - 95:00 immediately knows that it is an email field this one will hide it because it's a password this one provides me with the number information because it's a number and this one is just a string and if we switch over to signin you can see that it only has the email and password and we did all of this by just rendering a single form field which is then being created for each one of the mapped default values pretty cool right and finally we have the form description which I believe we can remove we already have enough
95:00 - 95:30 information right here we render the form message and then we can go here below the form to display a button of a type submit a class name equal to form BTN and we can say something like if is sign in then we can say let's do sign in else we'll do something like sign up there we go so now this is looking like a real form finally right there we go
95:30 - 96:00 and with that the UI of the form is now more or less complete the only thing we have to do is implement the upload image for the University card which we'll do soon but for now you can see most of the general Fields have been completed we can also check it out on desktop devices in my opinion this is looking much much better it's looking like a real professional slick form where users can sign in in with complete validation as well as sign up this path right here right now has a red squiggly line so we can actually
96:00 - 96:30 import it from react hook form so let's do that there we go so now we're good we no longer have any errors and I mean just look at how clean this form is we're defining it to be fully reusable we Define what the default values are what the submit function is and then we specify how it should be rendered so with that in mind we are now ready to start setting up the back end of our application including the database and more so we can actually fill in the information of this form and then submit
96:30 - 97:00 it through the handle submit function and create a record in our database which will of course be our user so let's do that next great work on completing the odd form just before we can create users iner database we need to deal with the last piece of UI and logic to be able to submit the signup form and that is the ability to upload the University ID card image and to make that happen we'll use image kit actually we'll use image kit
97:00 - 97:30 for all of the image and video handling in our entire application as it'll always remain completely free and give you 5 GB of free storage with 25 GB of media delivery bandwidth and not only that you'll also get a ton of image and video processing features from Transformations AI Generations optimizations streaming features and more of all the image upload services that I've used I found this one to be the best both in terms of features that it has to offer as well as the amount of
97:30 - 98:00 free stuff that they provide so click the link down in the description to be able to follow along and see exactly what I'm seeing and create a new account I'll sign up with Google then you'll get redirected to the onboarding so you can select your position and choose what's your main objective in this course I'll teach you how to use image kit to optimize and transfer your images as well well as videos as well as create some personalized media assets and overlays how to manage your content and
98:00 - 98:30 how to use apis to uploaded in more so we'll cover all of it finally you'll get your image kit ID in this case you can do something like JS Mastery and then you can put your name after it like Adrien and you can choose the region that is closest to you and click submit after that you'll get redirected to image kids dashboard you can hide the checklist as I'll show you how to do everything first head over to developer options and now we'll have to extract the keys for everything we need let's get the URL endpoint and then head back over to your
98:30 - 99:00 code and let's put those in our environment variables by creating a new file called env. local and let's call it nextore Public uncore Image kit uncore URL uncore endpoint and make that equal to the endpoint that we just copied after that we can get our public key so so we can put that as next Public Image kit public key and finally we'll also
99:00 - 99:30 need our private key so copy it from here and first you must set a password to do that so once you add a password head back over here you'll have to reenter that password and then you'll be able to copy that key head back to your envs and just say image kit private key and for this one you won't add the nextore public because this one will be used only on the server side now since there will be many envs we'll be using and their names are pretty long let's go ahead and create a new file called
99:30 - 100:00 config within the lib folder so that's a new config dots file and within it we can create a new config object that'll have the EnV object within it that will then have the imag kit object and here we can keep track of all of those envs such as a public key which will be equal to process. EnV do nextore Public Image kit undoru key you
100:00 - 100:30 get the idea right and now we can add the other two as well so I'll simply copy their names so that'll be URL endpoint is process. env. nexu image kit URL endpoint and finally we'll have the private key as well so let's define it as private key which is process. env. imagit private key and now we can export default. configuration so it's easier
100:30 - 101:00 for us to get access to these environment variables later on great now that we have those envs from the dashboard we'll have to head over to image kits documentation specifically on the next GS installation part so let's scroll through the steps first things first we have to set up image kit next gssd K so we can skip the steps where we're creating the app and we can head over straight to the installation of the package npm install image kit IO next I'll do that in our second terminal and
101:00 - 101:30 it's telling you here that we need to get a couple of envs which we have already set up after that it's telling us a bit about different components that we can use such as the image kit image which utilizes the next image and renders an image tag and others so let's go ahead and import a few of these into one of our components so I'll copy this and we'll move over to a new file which is going to be under components image upload and I will paste those
101:30 - 102:00 Imports right here we won't need all of them right now we'll just need the image kit image the image kit provider and the image kit upload and these are of course client side components so we'll have to turn the use client directive on right here at the top next we need to configure the app for image kit we'll have to do some styling and then render the images this is all looking great I think the documentation is very detailed but I'll teach you how to do that from scratch so let's go ahead and do it together first things first we'll create
102:00 - 102:30 an additional function which will authenticate us to be able to upload images securely I can do that by saying const authenticator is equal to an async function here we can open up a try and catch block where we get the error if there is an error we're going to Simply throw it by saying throw new error and then we can say something like authentic ation request failed and then at the end we can render the actual message in a
102:30 - 103:00 template string and rendering the error. message this error for now can be of a type any as we don't yet know how it looks like now in the tri block we can try to authenticate our users by making a request to a function which will execute on the server side so let's say const response is equal to await Fetch and now we have to make a fetch request to a specific domain name first of all I'll head over to our envs and I'll add an additional
103:00 - 103:30 environment variable called nextore public undor API uncore endpoint we'll change this later on in production but for now it can be HTTP Colin localhost 3000 and we can also add it to our config so head over to config and do it right here above image kit so that'll be API endpoint and it'll be equal to process. env. nextore public _
103:30 - 104:00 API uncore endpoint and also typescript might not know whether we have successfully provided these environment variables but we do know that so I believe if we add an exclamation mark at the end it'll actually consider it as given that there will be a string for each one of these enbs so now let's head back over to here and let's make a request to a dynamic route of config coming from lib config env. API
104:00 - 104:30 endpoint then we can do a forward slash API SLO and slash image kit then we can check if not response. okay we can simply open up a new block and get access to the error text by making it equal to a wait response. text and then we can throw a new error where we can say something like request failed with status and then we can just render the
104:30 - 105:00 response Das status and after that we can render the error text perfect outside of the if if everything goes right we can get access to the data by saying data is equal to await response. Json so what is our server returning to us and then we can destructure some of the things from the data such as the signature the expiration which is expire and then the token and these will come from the data object and finally we can
105:00 - 105:30 return an object consisting of just these three properties that'll be token expire and signature so now how can we create this function that will actually authenticate us well it is super simple we just have to create this route by heading over to app create a new folder called API within API create another folder called o within o create yet another folder called image kit and within image kit create a new
105:30 - 106:00 route. DS within it we can say const image kit is equal to new instance of image kit which is coming just from image kit so that means that we have to install this as well because so far we have just installed the next GSS DK so let me just run mpm install image kit on its own as well and then we can pass some configuration op options such as the public key and now we can very easily get access to this public key since we created the config object
106:00 - 106:30 coming from lib config env. imagit dopu key like so we can repeat that for private key so I'll just rename it here and here to private key and finally we need to do the same thing for the URL endpoint so I'll just do it here URL endpoint perfect now there is some code duplication here so if you want to destructure these fields you can you can do that by saying
106:30 - 107:00 cons destructure the environment variable key and then from it destructure image kit and then from it destructure these three keys so now we have kind of fully destructured that config object and now we can just use the variables themselves like so and if you have both the key and the value of the same name that means that you can just Omid the ladder and and it'll actually look just like this either way is totally fine once we create a new instance of image kit we can create a
107:00 - 107:30 function that we're calling by saying export async function get yep it is that easy to create backend API routes in next GS so let's return next responsejson and then pass over the image kit dot get authentication parameters and believe it or not that's it we have to add this to our server and it will return all the information we need to authenticate so now we can head back over to our image
107:30 - 108:00 upload and we can call this authenticator within our image upload component pretty straightforward right but now let's see how we can use some of these image kit next GSS DEC components I'll first wrap everything into an image kit provider to which we have to feed all of this information such as the public key equal to and now once again we need to get access to the same configuration options so we can just head over to our route and we can actually extract all of these because
108:00 - 108:30 we'll have to reuse them again so I'll paste them right here at the top so the public key will be equal to public key next we have the URL endpoint equal to you can guess it URL endpoint and finally we have the authenticator this time equal to authenticator which is the actual function that calls our backend to authenticate our user in this case we must not get access to the private key because this private key is available only on the server side and we'll actually get an error if we try to call it right here on the client side and we
108:30 - 109:00 also don't need to initialize the image kit right here as we're initializing it in that server action where we authenticate perfect so now we're wrapping everything with the image kit provider and within it we can render the image kit upload component that allows us to handle image upload I'll render it like so i k upload it'll be a closing component and to it we need to pass a ref so let's declare that reference right here at the top by saying cons ik
109:00 - 109:30 upload ref is equal to use ref this is of course coming from react and at the start it'll be equal to null later on we'll also have to have a state that'll help us keep track of the file we're uploading so let's create it immediately by creating a new use State snippet I'll call it file and set file at the start equal to null we can also Define the type of that state right here by saying that it'll have access to a file path of
109:30 - 110:00 a type string or the entire thing will just be null at the start this is just to help typescript make sense of what it is and you can already see this choose file upload right below for the time being I will actually provide a class name of hidden to this upload because it doesn't look best let's be honest so for that reason I'll provide it a ref of ik upload ref which will allow us to then render a nicer version of that same
110:00 - 110:30 component very soon it also needs two additional things which are going to be handlers for error and success so I'll say const on error will be equal to just an empty callback function for now and we need to do the same thing for on success so I'll just create it and rename it perfect and we can now pass them to this component by saying on error is equal to on error and on success is equal to on success finally
110:30 - 111:00 we can also pass the file name and we can say something like test upload. PNG at least for now later on we can change that dynamically now given that we're providing that reference we can actually trigger the click on this upload but when a bit of a nicer looking component gets clicked that we can style properly so let's actually run the under a button and this button will have a class name equal to upload D
111:00 - 111:30 BTN there we go it's looking similar to all of the other inputs within it we can render an image coming from next image with a source of Slash ions SL upload. SVG with an Al tag of upload icon with a width of about 20 a height of about 20 and a class name equal to object D contain so now we have a little upload icon and below it we can render a
111:30 - 112:00 P tag that will say upload a file and we can style it a bit by giving it a class name of text-base and text- light 100 and right below it if we have successfully uploaded a file so if file exists then we can render a p tag with a class name equal to upload Das file name and within it we can render the file do file path
112:00 - 112:30 for now we of course cannot see it but if it's there it would appear right here finally once it gets uploaded if it's there we can show it to the user by saying if file exists then render the ik image component this is actually a wrapper around the nextjs as image component so you can have all of the buil-in nextjs features as well as well it can accept things like the alt tag as all images do which can be equal to file. file path it can accept the path
112:30 - 113:00 to the image equal to file. file path and a width of 500 as well as the height of 500 as well now of course we cannot see it yet because we haven't uploaded anything but later on we should be able to see the uploaded file so let me try to fill out this form by first filling in my email I'll do some password as well let's enter a full name we can enter some kind of a University ID and then you can upload a file and then we can upload a file by providing an onclick function to this button I'll say
113:00 - 113:30 onclick is equal to a callback function where we get access to the click event I will run the e. prevent default to prevent the default behavior of the browser to reload on click and onclick I'll call the ik upload component to upload images by saying if ik upload ref exists or that current so if it has some kind of value in that case we can simply call ik upload ref. current question mark dot click so it's
113:30 - 114:00 like we're clicking this above component right here but rather we're clicking a bit of a nicer looking button I think there's a typescript issue right here saying that click does not exist on type never for the time being I think we can just suppress this by using the DS ignore on the line above so now if you click right here to upload a file your users will be able to find their University ID and double click to upload it currently we're not seeing anything happen because once we click and start uploading the file now is the time to
114:00 - 114:30 handle our on success and on error functionalities so let's do on success first we get a response of a type any and we can set file to be equal to the response that we're getting and we'll have to also modify the form values so we have to head back over to OD form and to this image upload component right here provide a prop of on file change is equal to field.on
114:30 - 115:00 change in a similar way how we're changing these input fields we also have to change the image upload field so now we can get into it these structure it from props by getting the on file change and let typescript know that on file change is of a type callback function that accepts a file path of a type string and returns void means nothing so now on success on top of setting the current state of the file we can also set the on file change and set
115:00 - 115:30 it to rest that file path and on error we can simply consol log that error by saying conso log we're going to get the error right here of a type any and we can just conso log it now of course users are not going to really notice those conso logs so it might be good to give them some kind of feedback on the screen like some kind of a toast at the bottom right so with that in mind we can quickly set up chatsi and toast component like this little popup that can appear by installing the chatsi and
115:30 - 116:00 toast I'll add it right into the terminal say use Force sure go ahead and now we have to add that toaster component to our main layout so let's just copy its import that'll be under our first layout we can import it right here at the top and then we can add it right here I I think they do it below the main within the body so that's below HTML but within the body toaster perfect and now how do we use it
116:00 - 116:30 well we just say use toast coming from hooks use toast and then we call it like this so let's do that by heading over to our image upload on success we'll do something like this Toast of course we have to import it right at the top so we can do that toast coming from hooks use toast and we won't say scheduled catch up rather we'll say something like image uploaded successfully and for the description we can say something like let's do Dynamic and then say rest. file
116:30 - 117:00 path uploaded perfect I can also duplicate this and add it on error so if something goes wrong we can say image upload failed your image could not be uploaded please try again and the variant will be set to the instructive perfect let's go ahead and give it a shot I'll try to upload a new file I'll choose my University ID and it immediately appears right here at the
117:00 - 117:30 bottom I think a driver's license should be fine with that in mind we have very easily integrated image kits image upload as well as image display properties now if you experienced an error while trying to show this image you might need to head over to next. config.sys another host name so that'll be protocol https and in this case the images will be coming from ik image kit. imagekit
117:30 - 118:00 doio and we can leave the port as empty perfect and with that in mind our form has now successfully been completed not only can we import all of the information like here like the email password and more all of it is of course fully validated we can switch to different types of forms and we can upload our University ID card since most IDs have more width than they do height you can lower the height to about 300 so it looks better and then we can successfully upload and see the IDS
118:00 - 118:30 before we go ahead and create our database and finally create our users within that database I want to show you how we can further optimize our images by heading over to our image kit dashboard head over to settings and under images you can see many different optimizations many of which have been pre-selected by default use the best format for image image delivery so it automatically selects it based on the image optimize image quality before delivery changes the image quality to reduce the image size without any visual
118:30 - 119:00 difference you can leave it at about 80 or you can decrease it much more and we can also do similar things with video as well as create different Transformations I'll show you how to use most of these later on with that in mind great work on completing the UI of the odd form in the next few lessons we'll focus on creating our own database and finally we'll connect the two this is the exciting part of the course let's create our serverless
119:00 - 119:30 postgress database click the neon link down in the description to be able to follow along and see exactly what I'm seeing and go ahead and create a new account you can use whatever either Google or GitHub is totally fine in this case I'll proceed with Google and immediately you'll be redirected to the on boarding where you can get started with neon for free you can start by choosing your project name start with JSM uncore and then we can do university- library or feel free to choose your own name after that you can
119:30 - 120:00 choose the database name as well I'll call it simply library and it also allows you to choose your cloud service provider as well as the region in my case I'll go with Europe but you can choose the region that is closest to you and click create project now the second step is interesting you can choose your own compute size which refers to the resources allocated for running the database server the vcp or virtual CPUs determines the number of virtual processors available for the database and memory just means Ram in this case
120:00 - 120:30 I'll let my Autos scaling range from zero all the way to two in this case I'll just say use recommended and just like that we are redirected to our neon dashboard but before we go ahead and set it up I want to tell you a couple of reasons of why I decided to proceed with neon for setting up our postcourse database see neon allows you to run a serverless database meaning that every from setup to your servers autoscaling and much more is in a serverless environment without it you would have to do a hell of a lot of setup for creating
120:30 - 121:00 servers using Cloud providers installing postgress there learning about permissions and roles and you would also have to give out your credit card before you can even understand how things work so if you have enough money to pay just to learn how to use postgress and you want to do everything on your own sure you can do that but if you're anyone like me who wants to share things faster while learning new things then together we can use neon postgress for free as you saw we were able to set up a whole
121:00 - 121:30 project in a matter of seconds you can have 10 of those with half a gigabyte of storage and 190 computer hours and on top of that you also get a lot of additional features like autoscaling doing database branching like you're doing branching on git great apis authorization levels and more but with that in mind let's see how we can set all of this up go ahead and copy your post connection string go back to your env. looc and add your database URL
121:30 - 122:00 right here perfect finally head over to our config and modify it to also include the database URL equal to process. env. database URL and now that all of that is set up in the next lesson we'll also hook it up with an omm known as drizzle what is an or object relational mapping tool allows you to make your app more scalable typically it slows your process
122:00 - 122:30 down while you set it up but drizzle on the other hand is made to ship ship ship so as with neon I have a lot of reasons of why we're using it it's designed for modern typescript developers type safe clean and Incredibly easy to use it allows you to connect to any database has a pretty pretty cool Studio where you can explore and manipulate your data not within an ugly postgress admin interface and it is a developer favorite so let's set it up click the link down
122:30 - 123:00 in the description and let's follow the tutorial for integrating drizzle with neon pogress we already have an existing xgs project so we can go ahead and install and set up drizzle orm and drizzle kit we can copy those two commands and paste them into the terminal mpmi dzm and mpmi D drizzle kit once those get installed we can also install the neon serverless driver and if we want to we can also have a package that helps us manage our envs but we
123:00 - 123:30 have already created our EnV configuration and it says here in case you face the issues with resolving dependencies during installation if you're not using react native Force the installation with force or Legacy peps and now we have a couple of steps to set everything up log into neon console and create a new project and create a new project we've already done that I believe I called my database Library set up the connection string variable we have already done that as well and added it to our EnV local in the previous
123:30 - 124:00 lesson finally we have to connect our jel orm to our database by creating a new drizzle. TS file so let's copy this code head back to our code and create a new folder in the road over application called database within database create a new file called drizzle. DS and within it you can paste the code that we copied we'll be getting the config object not from EnV but from add for/
124:00 - 124:30 libconfig here are all of our environment variables and it is a default import so we can remove those curly braces then we import drizzle and then we can say something like this const SQL is equal to Neon which we can import from neon DB serverless database and to it we can pass the config env. database URL finally we export that database by calling drizzle and then we can set the client to be equal to this
124:30 - 125:00 SQL database the next step is to declare schemas in this case drizzle is insulting us and telling us that we're building a to-do application but as you know we're building a University Library management system but still we'll take what they give us and then modify it so let's create a new file right here in the database and let's call it SK .ts and I'll paste the code that we copied but in our case it'll have to look a bit more advanced I'll keep this
125:00 - 125:30 ID here but remove the other two properties and make the ID a bit more advanced check this out instead of using integer we'll be using uid which stands for a unique identifier and we'll give it a name of ID we can also specify that this property should not be null and that it will act as the primary key of the rows in the database also by default it'll have a random value so we can say
125:30 - 126:00 default random and it has to be unique since it's a primary identifier or a primary key and we can import this uid coming right here from drizzle orm perfect and I think this shows you why we're using drizzle in the first place it makes it super simple to specify the architecture of your database very complex SQL databases for that matter in simple JavaScript code and you'll see later on once we migrate over to Native SQL commands of how much
126:00 - 126:30 more complex it'll get for now let's continue by providing a full name it'll look something like this we can also say that this full name will be a VAR car this simply means that it's a list of characters and the name of that column will be foore name which is not null so why have I decided to use full uncore name which means that this is snake case and not regular camel case even though we use camel case in JavaScript most
126:30 - 127:00 often when you're creating SQL databases you typically want to go for snake case meaning lowercase letters divided by the underscore separator great let's also import varar from the top and let's give it a maximum length of about 255 characters great next let's let's move over to email which will be a type of text with the name of email which is not null and is unique we can also Define a
127:00 - 127:30 University ID which will be an integer with a name of University ID and it will not be null and it also has to be unique after the University ID we can Define the password field which will be of a type text password and it must not be null we can also have a university card which will be a URL pointing to the
127:30 - 128:00 image of the University card so we can have a text property of University card which is not null doesn't have to be unique and we can also have a status of that specific student and I just noticed right here we're not working with to-dos which is what be copied over from here we're working with the users within our database so let's rename this over to users and make it a PG table of users right here as well the status itself will actually be an enum so let's say
128:00 - 128:30 export status uncore enum and an enum simply means an array of different strings that are possible answers to that status so the values that can be used we can declare it by saying PG enum like this which we can import from PG core and then to it we can Define the name which will be simply status and provide an array of different values such as pending approved or
128:30 - 129:00 rejected we can also have another enum for the role of that user which we can export and call roll enum it'll follow the similar structure PG enum of roll and it'll be something like user or admin so to keep it simple let's have user and then admin all uppercase like this finally let's have one final enum not for users but for books so it can be
129:00 - 129:30 borrow status enum which will be equal to PG enum of borrow status with an underscore in between with the values of let's keep it simple and just say something along the lines of borrowed or returned there we go so now we have three different enums which we exporting right here and of course we have to say export const and then the name of that enum perfect you can see prettyer automatically put it into new lines for
129:30 - 130:00 me which is okay and finally we can now use that enum when declaring the user table so here we can say status is going to be equal to status enum which we call like this pass it the name of status and we can choose a default value for this enum by default the status will be set to depending we can then define the role of the user by saying Ro enum of the name role and choose the default value of user it might be useful to track the last
130:00 - 130:30 activity date so I'll set that over to date and I'll call it last activity date which can default to now so we can Define it something like this default now of course we have to import date right here from PG core as it's another type and finally we can also have a created ad property so we can know when the user joined and that'll be equal to
130:30 - 131:00 a timestamp coming from PG core and it'll be called created at and we're going to say with time zone will be applied to true and we can default it to default now perfect so this is the structure of how our us users are going to look like in our database we have defined all of it using drizzle so what is the next step well as you can see right here in the docs they
131:00 - 131:30 say that the next step is to set up the drizzle configuration it is a file used by drizzle that contains all the information about your database connection migration folder more on that soon and schema files so let's create a new drizzle. config.sys outside of everything and I'll call it jizzel DOC
131:30 - 132:00 config.sys more carefully so let's actually open up a new terminal window and run mpmi EnV and if you face any kind of issues when installing some dependencies like it happened in this case looks like my esin has been bugging me simply add the-- Legacy Pier depths after the installation command so it's going to look something like this npmi EnV --
132:00 - 132:30 Legacy - beer- deps and it'll install it right off the bat now that we've installed the EnV package and we're getting the defined config from jizel kit we have to set up our configuration path currently by default it is set to EnV but as you might know we have stored our environment variables within a EnV local file so simply add the local right here so it points to the right place after that we have to Define where our
132:30 - 133:00 schema is located it is not under Source DP schema TS it is actually under database schema. TS the out folder is where our migrations will go you'll see how that looks like soon and then we have to choose the dialect of our database language in this case it is postgress and we have to add our DB credentials in this case they're coming from process. env. database URL so it looks like we're good with the step five so what's going
133:00 - 133:30 to happen once we try to run the commands listed in Step six it says you can generate migrations using drizzle kit generate command and then run them using drizzle kit migrate the concepts of generate and migr are very important database Concepts which drle didn't invent they have existed for a long time time on many different ORS and once I actually run these commands you'll see exactly what they do you'll see their visual representation
133:30 - 134:00 so let's go ahead and generate those migrations by copying the MPX jle kid generate command and pasting it into our terminal this will very quickly create a new SQL migration under the migrations folder right here and there we go create table users uu ID primary key default gen random uid what is this syntax well it's definitely not JavaScript in fact if you check out the file name you can see that the extension ends with SQL so
134:00 - 134:30 this is native SQL code that's necessary to update our database schema and it got generated based off of our plain JavaScript code which looks a lot like using Zod which means that it is familiar simple and quick now does this mean that you should not learn SQL as a full stack or backend developer not at all learning it will definitely be of great help but using drizzle to quickly spin up databases and projects will save
134:30 - 135:00 you so much time so alongside this SQL file drizzle also generated a meta folder that contains a snapshot of the database at that specific point in time the next thing we have to do is run the migration so let's copy this command and paste it into the terminal MPX drizzle kit migrate and there we we go even though there's a warning right here you don't have to worry the migration ran successfully and if something went wrong it is mostly a problem with your schema
135:00 - 135:30 so please make sure that it is exactly like this with all of the enums exports and more and then you can try to regenerate and remigrate if that fails to you might want to create a new neon project just like I did and then you can use it within your envs hopefully that should help with any potential problems but as you can see in my case everything went smoothly on the first run so what did the migr command do well it applied all the changes like new tables columns or relationships and these are the
135:30 - 136:00 commands that you'll have to use quite often when using drizzle remember you have to create schemas then generate respective SQL commands using drizzle generate and then apply those commands to changes to the database using drizzle migrate so if you want me to do a detailed video just on how drizzle works in the future let me know down in the comments and I'll do that but running these commands every single time is a bit of a pain so instead of manually typing it out and trying to remember it we can head over to our package.json and
136:00 - 136:30 modify the scripts to run these commands for us so right here we can say DB generate will run the MPX drizzle kit generate after that we can do something like DB migrate which can run MPX drizzle kit kit migrate and finally we can also run the DB Studio which will open up the drizzle kit Studio that'll look
136:30 - 137:00 something like this so far we have tested these two but we still haven't yet tested out the studio so let me actually show you how that works I'll head over to my terminal and run mpm run DB Studio it says drizzle studio is up and running on local. drizzle. studio so let's actually open it up and it just opened up in my browser I'll expand it so you can see it in the full View and there we go think of this as your modern SQL database interface where you can run
137:00 - 137:30 different SQL commands to query the data from the database check out all the rows and columns and Records even manually add some data or alter the tables drop them and do so much more pretty cool right all of it running locally within your browser in a modern interface of course there is so much more that you can do and explaining all of the features would require a separate video on drizzle so as I said if you want me to do that I'll gladly do it just let me know down in the comments but for the
137:30 - 138:00 time being to test out the connection between our database and between our application let's add a simple user I'll go ahead and add a record of full name I can do something like Adrian JS Mastery right here email can be something like contact JS mastery. proo University ID has to be unique password University card what else do we have status role last activity created ad can all be default so let's actually fill in the email
138:00 - 138:30 that's going to be contact jm.pro as well as the University ID I think you have to press enter once you actually save it and let's try to save this record and there we go we now have one real record within our database so let's head back over to our homepage that's going to be page root DSX and let's try to fetch that user because doing it is super simple I'll turn this into a regular function that has a return block
138:30 - 139:00 that'll look something like this and then right here at the top we can say const result is equal to a weight of course we have to make this an async function if we want to use a weight DB coming from at database SL drizzle. select. from and here we can select from users users will actually be a schema coming from database schema super simple right
139:00 - 139:30 give me all the users and then we can console.log json.stringify result and then pass in null and two to create some more space in between the results even though right now it'll be only a single result so if we get something in the console it means we have successfully established a connection with database so back on the homepage I'll reload and would you look at that we get back one user with its uniquely generated ID activity date and
139:30 - 140:00 created ad as well as some default enums for the status and role University card ID password email and even full name it is all here and we're fetching it directly from the database Isn't that cool in this lesson you just learned how to set up a serverless postgress database yep that's right you've done everything from creating different schemas for the user table to then running the migrate and generate commands which created an SQL
140:00 - 140:30 counterpart that postgress can actually read to create a real database then we have added those commands right here in the package.json so we can more easily call them in the future whenever we decide to change our schemas and finally we explored drizzle super convenient Studio that allows us to modify the database in real time which we did by adding an additional record then I showed you just how simple it is to fetch the data from that new postar database that we've created all it took
140:30 - 141:00 was a single line of course setting up databases especially SQL databases typically takes a long time and you get an interface that doesn't really look like this it's clunky and old and the only reason why we're able to set this up so quickly and so conveniently is because we use neon neon spun up a serverless instance of her post database gave us a connection string and that's it you can also track a lot of additional stats right here and even check the database right here with a neon as well you can see this looks very
141:00 - 141:30 similar to what we saw in drizzle Studio there's also an overview and one of neon's best and unique features is their branching allowing it to create g-like branches for different instances of your database you can simply give it a branch name give it your parent branch and choose which data you want to include you can include all the data up to this point or just some specific logs or dates and times and that said you have another version of your database right here right now and let me know in the
141:30 - 142:00 comments down below if you'd like me to see do more full stack projects using SQL databases such as postgress with neon over something like mongodb I found it to be super fast to spin up new projects and I'd love to do many more using it with that in mind we have successfully built out the homepage as well as o pages of our project let's not forget we have also hooked up a real database and created a user schema so naturally that leads us to The Next Step
142:00 - 142:30 which is to hook up the existing UI of the oth with the user schema in our database so we can actually create new users and onboard them to our app so let's do that next let's set up authentication since this is a production ready application we'll use the best free and open source off OJs so simply head over to their getting started guide and move over to installation with nextjs and let's go ahead and follow the steps we can run
142:30 - 143:00 mpm install and then copy the rest of the command I'll do it right here in my second terminal oh we'll need a third one because we are running both the drizzle Studio as well as our application before running this command also add another package which we installed and that's going to be bcrypt JS also for some reason in this project I have some issues with es lend so I have to add-- Legacy Das beer- deps you might not need to do that if it throws an error then you can but other than
143:00 - 143:30 that you can just run mpm install and let's wait for those two packages to get installed now while they are getting installed I want to explain a bit about how we can configure OJs there are two different ways off configuring it one is with something known as adapters if I expand the list of adapters right here you'll see that it includes everything from drizzle to different types of databases like mongodb postgress and even up stach redis which we'll explore in more detail soon an adapter in next o
143:30 - 144:00 basically connects your application to whatever database or backend system you want to use to store your data for user accounts and sessions in our case we could use drizzle because we're using it as our orm of choice with these adapters we have automatic database handling and type safety ideal for standardized and low maintenance approach but if you decide to go without an adapter it's definitely more flexible but it requires custom coding for the entire
144:00 - 144:30 authentication logic but it allows for more straightforward database calls to store users and do other things to be able to make a decision of which route you're going to go it's important to consider your project requirements in our case we have just a very simple email and password mechanism with no social odds like Google or GitHub and more so in this case we can go without using an adapter since the user will only have one account not multiple accounts but if you're interested in learning how you can manage multiple user accounts like emails passwords
144:30 - 145:00 Google GitHub and more check out my ultimate nextjs course where I teach all of these things in detail I even remember creating dedicated diagrams to explain the entire authentication flow you can see how many lessons there are and then we go into authentication 2 for social off accounts and then finally email and password authentication there's a lot but you don't need to know any of that for this course so back in our terminal you can see that our packages successfully got installed so
145:00 - 145:30 let's go ahead and create a new file called O.S in the root of our project within here we can set up the credentials provider but just before we set it up let's actually follow the steps outlined in the installation process where we have to run MPX off secret to create a mandatory environment variable it'll looks something like this immediately if you head over to your env. loal you'll be able to see a new odd secret right here now that we have that we are ready to configure the odgs
145:30 - 146:00 file and object where we control the behavior of the library and specify custom authentication logic or choose adapters create a new odds file which is what we have already done and then get started quickly by pasting the following contents import next OD we get a lot of things out of it like the signin functionality sign out off and more and we can choose which providers to add before we get started with the second part of Step number
146:00 - 146:30 three which is adding a new route Handler let's actually go ahead and set up our custom authentication logic right here within the next o config first things first I'll say that we will manage the session using a strategy known as JWT meaning Json web tokens then under providers we can provide a credentials provider which will look something like this you can import this credentials provider right here at the top by saying import
146:30 - 147:00 credentials provider coming from next- o SL providers SL credentials then once you call it you can pass an object into it which acts as the configuration within there you can Define some methods you can call such as an asyn function of authorize which takes in the credentials and then we can do whatever we want with those credentials first things first we want to check if there
147:00 - 147:30 is no credentials question mark. email or no credentials question mark. password we simply want to return null meaning there's nothing there but if we do have an email or password we are ready to try to fetch the user from the database by saying const user is equal to await DB coming from database drizzle do select so we want to select from where from users this users can simply point
147:30 - 148:00 to the schema of users where EQ which means equal coming from drizzle orm email is equal to credentials. email. to string so we're checking whether the email field matches or specifically this should be the users. email if the user's email matches then we want to return that user and we're going to limit it only to one because there can be only one user with that specific email if this query returns nothing
148:00 - 148:30 meaning if user. length is triple equal to zero then we can return null else we can check the validity of the password by saying const is password valid and that'll be equal to a wait compare credentials password. twring with the user zero meaning the first user we fetch do password so we're comparing those two
148:30 - 149:00 and the compare function is actually coming from the package we installed after installing nextto and that's going to be bcrypt so import compare from bcrypt JS it looks like we also have to install the types for the bcjs package so let's copy this error message head over to the terminal and say mpmi D- save-dev bcrypt JS if you do that the error will be gone great so now that
149:00 - 149:30 we're checking for the validity of the password if the password is not valid we can simply return n once again but if we have successfully passed all of these checks we can return an object containing the ID which is going to be equal to user z. id. TW string we're going to also pass the email equal to user0 doil and the name of the user which is going to be equal to user z.
149:30 - 150:00 full name make sure it is full name like this even though the field in the database looks like this full uncore name here we're declaring it with regular full name in typical JavaScript camel case format and we can say that this is as user coming from types. D.S saying that this will follow the structure of the user but it will not be a user coming from our types it'll actually be a user coming from next off so let's import it right at the top we
150:00 - 150:30 can do that by adding a comma right here and then getting access to the user type from next off which has the ID name email and image great with that in mind our authorized method under the credentials provider is now complete so we can exit the provids array and we can find the pages where we'll be doing the off the sign in page will simply look like this SL sign- in and we also need to define the callbacks within callbacks
150:30 - 151:00 we can have an async JWT function that accepts a token and a user and it'll check if a user exists then set the token ID to be equal to the user ID and set the token. name to be equal to user.name once you have set those values return the modified token alongside this async JWT we can also create an async session function which accepts a session and a token and then if a session exists
151:00 - 151:30 so if session. user then we can set the session. user. ID to token ID as string just for typescript to be safe as string here as well and set the session. user.name to token. name finally we can return the session we do this to populate the token as well as the session with the currently logged in user yes there is some manual work you have to do if you don't use an adapter
151:30 - 152:00 but you have more flexibility with that in mind we have now completed the first part of the third step and now we have to create a route Handler we can do that by heading over to our app API o and then next to image kit we'll also create a new folder and I'll call it o within o we need to create a file called square brackets dot dot dot next o square brackets so let's create a new folder
152:00 - 152:30 with that name and then within that folder let's create another file called route. DS just following the docs here and then within it you can copy and paste the code provided to you from the documentation after that we can add the optional middleware so let's do that by creating a new file in the root of our application called middleware DS and to it you can copy and paste this line finally we need to set up
152:30 - 153:00 authentication methods with different providers in this case we won't be using any providers or rather we'll be using just one provider and that is the credentials provider meaning email and password login which we have just set up so now that we're done with this installation guide let's actually create a server action for signing up our users we can do that by heading over into lib and create a new folder called actions and within actions create a new file called off. DS within this and
153:00 - 153:30 other server actions file in nextjs there is one first very important thing you have to do and that is add the use server directive at the top the used server directive tells this file and the rest of the code that whatever function you have here like const sign up is equal to an async arrow function will only be called on the server side not
153:30 - 154:00 the client side and this is important because whenever you're making database call or database mutations those calls have to be very secure so not all of these environment variables can be accessed from the client side like the image kit private key or your database URL those are for server eyes only right here into this function we are accepting params and those params can be of a type O credentials and we can add those o
154:00 - 154:30 credentials right within our types. D.S below the book by defining a new interface called o credentials and then defining the fields that it'll have it'll of course have a full name of a typ string an email of a type string a password of a type string string and a University ID of a type number as well as a university card which will be of a type string as it'll be a URL to an uploaded
154:30 - 155:00 image so now we're accepting it right here and let's put it to use by destructuring all of those values by saying const full name email University ID password and University card and those are going to be equal to params now let's try to fetch an existing user if one was created already we can do that by saying cons existing
155:00 - 155:30 user is equal to await DB coming from database drizzle do select from users where EQ meaning equal users. email is equal to the email that we're checking for right now and this EQ is coming from drizzle orm so at the top let's say import EQ from drizzle dasm and we're going to limit it to one user
155:30 - 156:00 perfect now if an existing user. length is greater than zero it means that it already exists so we can either throw an error or just return an object that says success is false and error saying something like user already exists but if a use user doesn't already exist we can hash the new user's password by saying const hashed password is equal to a weit hash coming from bcrypt JS to
156:00 - 156:30 which we can pass the password and then something known as salt which is the complexity upon which it'll be hashed then we can open up a try and catch Block in the catch we get access to an error and we can simply console that error or console log that error and we can say something like sign up error right here and return an object saying success is false sign up error
156:30 - 157:00 but in the try we can try to create a new user and let me show you how simple that is you can just say await db. insert into users the values and then Define the values you want to insert such as a full name email University ID password equal to a hash password and the university card and I just noticed I have a typo right here with the password webstorm also let me know that so this was supposed to be password right here
157:00 - 157:30 and here and that's it that's how you create users and later on upon registration we can also automatically sign in on the behalf of the new user so they don't have to provide their email and password again on the signin screen by doing something like a wait sign in with credential and then pass in the email and the password this sign in with credentials is a function that we are yet to create once we implement the signin server action and finally after that we can
157:30 - 158:00 return a success of true perfect so while we're here let's actually create that function for signing in I will uncomment it here and create it right above by saying const sign in with credentials is equal to an async function that accepts params but it doesn't accept all params so here I actually want to teach you an advanced typescript thing where you can still say
158:00 - 158:30 that it'll be all credentials but then you can apply a pick parameter and then pick only some of the types that will be there in this case it'll be email as well as password because it is sign in and not sign up so let's destructure those values by saying con email and password is equal to params and we can open up a try and catch Block in the catch we'll do the same thing as before conso log sign in error
158:30 - 159:00 success fals sign in error but in the try we will simply sign our user in and once again next off or OJs makes it super simple to do that by saying const result is equal to await sign in and this is coming from add SLO to which we need to say that we want to sign in using the credentials method and then pass in the email the
159:00 - 159:30 password as well as the redirect set to false finally if result question mark. error is true meaning if error exists we can return false and then pass in the error else if we do everything successfully we can return success of true so we can now collapse this function let's not forget to export both of these so export cons sign in with credentials as well as export con sign up and let's head over into our signin
159:30 - 160:00 page that is this one right here under app o sign in here we have to pass the function we just created which will happen on form submit simply put it'll be sign in with credentials and let's not forget to head over to our sign up page as well that'll look something like this and here we can simply call sign up coming from lib actions o now we'll have to head over into the O form. TSX and
160:00 - 160:30 modify it to implement the toast functionality we already used the toast component on image upload now we're going to use it here as well to let the user know whether they have successfully or unsuccessfully signed in or signed up so let's head over into the handle submit function and here we can submit the data by saying const result is equal to await on submit and then pass the data that we got from the form and if the result is a success then we simply
160:30 - 161:00 want to display a toast with a title of success as well as a description of if is sign in then we can do you have successfully signed in or we can do something like you have successfully signed up so it'll look something like that and Fin finally after signing the user in we want to use the router functionality so right here we can say const router is equal to use router and then we can say router.
161:00 - 161:30 push and push over to the homepage because we have signed in successfully and don't forget to import the toast coming from add SL hooks use- toast and don't forget to import the used router coming from next navigation okay that's good and in case something fails we can add an else and then present a user with a toast that'll have a title of error if we're signing in so if is sign
161:30 - 162:00 in in that case we'll say error signing in else error signing up and for the description we'll simply put the result. error and a variant will be set to destructive great and there's one very very important thing we need need to do before we can test all of this out and that is to head within our primary layout within the app folder Yep this one right here where we added the toast and we have to wrap it with something
162:00 - 162:30 known as a session provider this is given to us by next o so let's wrap it properly just the body and you can see it is coming from next o react and to it we have to provide a session equal to session now where do we get this session from once again it is super convenient we just say concession is equal to await o and this o is actually coming from the O file next o does everything for us let's
162:30 - 163:00 not forget to wrap it into a sync since we're using a wait the error is gone and we are ready to test it out I got to go full screen for this first let's create our library account for that we'll also need a University ID so somewhere on the figure design down below you can find this nice looking card and if you copy this entire figma design to your own repo you should also be able to modify it by double clicking on specific words and then changing it right here of
163:00 - 163:30 course if you have that font if not you can change the font as well once you're there simply select it scroll down right here and Export it as PNG then you can head back and upload it right here there we go that is looking credible so now I can also enter my email I'll enter my password for full name and my University ID you can think of whatever what matters more is the University card and before I actually create this account I want to head back over to our drizzle studio and remove the current user that
163:30 - 164:00 we have here we don't need it anymore so with all this in mind let's go ahead and click sign up and just like that we got redirected to the homepage and there was some kind of a toast right here to the bottom right but it didn't look that good it looked like it was missing some Styles we'll look into that soon but what matters more for now is that we actually got redirected back to homepage now what would happen if I try to reload okay we're still on the homepage what if I manually head over to sign in
164:00 - 164:30 oh we can visit the signin which typically means that we're not logged in right if you can go to the signin page if you're signed in you should never be able to see the odd page so to achieve that we can add redirects head over to your layout. TSX within o and just look for the active user session by saying const session is equal to await o and then if there is an active session
164:30 - 165:00 simply redirect this is coming from next navigation to forward slash meaning homepage we also have to turn this into an async function and we'll also add this redirect over to our other layout right here within the root but here it'll actually be the other way around I'll say try to get the currently active session and then if there is no session redirect over tosign in because we want our users to
165:00 - 165:30 be signed in first in this case if I reload you can see I stay on the homepage which means that we're successfully logged in and there is an active session for this user but let's also indicate that there's an active session in another way maybe by showing the user Avatar if the user is logged in right here at the top right I'll do that right here within the header component by simply heading right here below the library link and adding a new Li that's
165:30 - 166:00 actually a link with an HF of my profile which means we'll have a my profile page too and then within here we can render a shaten avatar component which is an image element with a fullback for representing the user we can install it very simply by running MPA shat and at latest add Avatar and we'll have to add the Legacy Pier depths and once it gets installed we can simply import it right here at the top and then use it like
166:00 - 166:30 they say right here it looks like it didn't get installed properly so I'll just rerun MPX shatan latest at Avatar one more time without the flag oh and then it's going to manually ask me if I want to apply that flag so I'll say yes which will install the dependency and the immediately you can see this Avatar right here at the top right now we have to get full user data right here into this Avatar component and we can do that by heading over to where we're calling the header
166:30 - 167:00 component and I think that's in the root layout so let's head over into the root layout and here we're calling the header so the only thing we have to do is simply pass the session into this header then in the header we can accept it by saying session of a type session of session coming from next odd and then we can simply render that users credentials in this case we don't have an image so I
167:00 - 167:30 can remove this Avatar image as we don't need it and instead I can render the Avatar fullback which will be session question mark. user question mark. name right now we cannot really see it as it's dark on dark but we can give it a class name of text- white and it looks like the text is too long to fit into this small circle so to fix it we'll actually have to just get the credentials of the full name we can do that easily by creating a new utility
167:30 - 168:00 function let's say export const get initials is equal to a function that takes in a name of a type string and it'll also just return a string but it'll be a modified string which only contains the initials we can do it in line by saying name that split map each word get only the first character of each word join those words and make them uppercase and then slice them to only two characters so I can say
168:00 - 168:30 do slice 02 in case some names are a bit longer now we can head back here and we can call this get initials function and pass the full name into it and typescript is complaining since we're not certain that the name will be there so if the name is not there we can just provide High n for initials maybe but in this case you can see I got a and then straight line as I typed Adrian JS Mastery for you it should be your initials and to match it with the design we could also maybe change the color of
168:30 - 169:00 the background instead of changing the text color I'll do that by changing the color to something like BG let's do amber 100 I think this is a nice color and there we go now it's popping up a lot more in case you want to do something else you could also type in your first name here so that's going to go outside of the Avatar like it's on the design but I actually don't think it's needed just keeping the Avatar Photo is looking good and to finalize the entire authentication flow we need a log out function that way our users will
169:00 - 169:30 be able to log out and we'll be able to test out the entire authentication functionality once again to do that let's actually implement the page that we can get to when the user clicks on their Avatar Photo that's the my profile page so we can add it right here within root create a new folder call it my profile and then within it create a new page. TSX run rce immediately you should be
169:30 - 170:00 able to see an empty page appear and there we can renter an empty react fragment that'll include a form this form will include only one button that'll say log out now to keep this page server rendered we can actually utilize next js's and react act new server forms by passing an action into this form and declaring it like this async callback function and then giving it the use
170:00 - 170:30 server directive right here at the top this will be called directly on the server even though it's a click an onclick Handler pretty cool right so here we can await sign out functionality coming from at SLO let's also give it a class name equal to margin bottom of 10 and below this form we can render a book list we can pass a title of something like borrowed books and then for books we can render all of our sample books
170:30 - 171:00 for now these sample books are coming from constants but later on we'll be able to fill this in with the real books that the currently logged in user has borrowed with that in mind let's close all of the currently open files expand our application check out how nice it looks like on the homepage admire the 3D book we have right here with this SVG cover head over to our profile page and log out that works as we immediately get redirected back to signin now if you
171:00 - 171:30 want to you can manually head over to the home but you'll automatically be redirected to sign in because there's no active session you can head over to create an account but in this case we already have an account so let's try it out by specifying your email and your password and if you do that you'll be able to go back to the homepage immediately and yep I noticed that toast component didn't look good even though we didn't modify any of the original shat CN code it remained completely
171:30 - 172:00 intact right here under toast components so it's interesting that it doesn't have the background what we could try is simply reinstalling it so I'll head over to the terminal and say MPX shcn at latest add toast I'll use the Legacy per depths it says the file or already exists would you like to overwrite I'll say yes use so already exists or write that too toaster exist too yep do all of it and now we have the latest versions of the file so if I head back over to
172:00 - 172:30 our app one more time and try to log in just to see whether it fixed it it looks like it still hasn't so this sometimes happens which means that we'll have to manually modify the toast component don't worry about that it's not as scary as it seems we'll do that later on once we start adding some other cosmetic upgrades for now we have bigger things to focus on finally the entire homepage authentication and database setup have been integrated so in the
172:30 - 173:00 next lesson I'll teach you how to make your app even more scalable and production ready let me show you how to make your app truly production ready and I really do mean that if you check out the credentials part of the OJs documentation the library we use to Implement authentication you'll see that it says that by default the credentials provider the one we used for emails and passwords does not persist data in the database however you can still create
173:00 - 173:30 and save data in your database which is what we are doing you just have to provide the necessary Logic for example to encrypt passwords for that we used btjs to add password reset functionality you can do that using any kind of an email provider and to add rate limiting so what is this rate limiting it's not something that I hear about often in web development courses or tutorials even though it's not often mentioned in entry level tutorials it is something that is
173:30 - 174:00 implemented on almost every single professional application they wouldn't function without it if many users or one malicious user tries to make too many requests to a certain page or API they'll get redirected to a 429 too many requests client error response indicating that the the client has sent too many requests in a given amount of time this mechanism of asking the client to slow down the rate of requests is commonly called raate limiting and it is done to prevent DDOS attacks or the
174:00 - 174:30 denial of service attacks cyber attacks where perpetrators seek to make a machine or network unavailable to its intended users by temporarily or indefinitely disrupting Services by making too many requests how dare they right so today I'll teach you how to implement rate limiting into your applications to prevent DDOS attacks and we'll do that and more using up stash a serverless data platform that offers a
174:30 - 175:00 serverless reddis database which you can use for free to cach your data make efficient searches or even rate limit certain devices they also offer vectors for managing numeric representations of objects such as images sounds and text often used in AI applications to build and train your own algorithms and the third very important feature that up stash provides are Q Stash's workflows which allow you to schedule and run workflows such as for onboarding users like dual lingo or sending notifications
175:00 - 175:30 after an event has happened on your website all of this is completely open- source and used by many developers many of you have already requested it so in this course I decided to teach you how to make the most of appstash reddis and its other features if you haven't already click the link down in the description go ahead and do that and create your account once you do that you'll be redirected to your appstash dashboard so let's go ahead and create a new reddis database if you don't know what reddis is it's a super fast
175:30 - 176:00 database used for caching real-time data and managing temporary data so your initial thought might be hey we're already using postgress with neon why do we have to add another database well we'll only use it as a secondary database to optimize our application and if you're wondering what up stash rdit is it's similar to what neon postgress is a serverless version of redis that automatically scales and is incredibly easy to set up so let's give it a name I'll call it a University Library you can select your primary region and click
176:00 - 176:30 next as you can see it is completely free and it'll forever remain free for up to 10,000 daily commands so let's click create and now we can copy up Stash's endpoint and token we have them right here under rest API so copy the first one head over to your env. loal and add it right here below you can call it Upstore red isore URL and then we can do the second one which you can
176:30 - 177:00 call up stash reddis token there we go so now our rest API has been set up we can also head over to our config to actually leverage those two envs we can do it by adding up stash right here here as a new object and then we can attach the Redd URL to be equal to process. env. Upstore risor URL and we can also add a reddis
177:00 - 177:30 token which is equal to process. env. Upstore risor token now what we want to do is rate limit the requests made to off pages so people won't spam them we'll create a rate limiter in a way that you can use it in any server action or API route so you can add a rate limit to anything and completely secure your application to achieve that we'll follow the documentation from up stash just head over to sdks rate limit getting started
177:30 - 178:00 the first step is to install the rate limit package from up stash so I'll copy the command and paste it right here now we can add rate limiting to our endpoint but before we do that you'll notice that we'll also have to have another package installed and that is is app stash rdis so let's install that one too that'll be mpm install appstash RIS there we go it was super quick and now we can copy this code block and add
178:00 - 178:30 it over to a new file within the lib folder which I'll call rate limit. TS and paste what you copied right here now instead of using this redis from our environment variables we can also set up a new redis configuration instance by heading over to our databases folder and then creating a new file called rus. TS within here we can set up our configuration it's pretty simple to do we can just say con redus is equal to
178:30 - 179:00 new redus and this redus is coming from appstash rdus and to it we can pass the config by defining the URL equal to config coming from lib config env. up. rdus URL just like this as well as passing the token equal to config env. up. rdis token finally now that we have created this reddis instance we can
179:00 - 179:30 export default redis and now we can head back over to our rate limit file I can remove all of these comments because we don't need this many and I will actually explain everything that is happening here there we go we won't be needing any of this as of now what we will need is a rate limit instance which you can create by calling the new rate limit from up stash rate limit and then pass the configuration option to it in this case redus will just automatically come from reddis which is
179:30 - 180:00 the instance of the redus that we just created so we can do it like this then you pass the limiter and in this case you can choose a sliding window or you can do something like fixed window where you can Define the number of requests that can be made on a specific point in time like maybe five tokens per one minute if you hover over it you'll be able to see that a param of token actually specifies to how many requests a user can make in each time window and then you have a fixed time frame right
180:00 - 180:30 here and we can leave the rest of the stuff as it is analytics the true and the prefix of up stash rate limit finally you can export default this rate limit instance just so we can use it later on when you're trying to rate limit some parts of your code and we can remove this comment as well as the UN used import also rate limit is a valid word so right here I can save it to the dictionary so it's not yellow and so is up stash so we can add it to web Storm's dictionary so it always knows what up
180:30 - 181:00 stash is because we'll use it in many upcoming applications let's rate limit our first server action it'll be the sign up function within o actions so head over to lib actions .ts and sign up with then here right at the top we can get access to the current user's IP address by saying const IP is equal to wrap it in parenthesis and say await headers which you can call like
181:00 - 181:30 this coming from next headers and then call the dog get method on it to get the X forwarded for or you can use the default IP address of 1270 01 so what we're doing here here is getting the current IP address then we can try to get something out of the await call to rate limit this is the instance of rate limit that we created
181:30 - 182:00 to which we need to pass the IP address like this once again rate limit is coming from rate limit TS file oh and I just noticed I misspelled it it was supposed to be rate limit so let me rename it to rate limit. DS and specifically we want to use the limit method on the rate limit instance by saying rate limit. limit this specific IP address once we do that we'll get back a success variable which will let
182:00 - 182:30 us know if we can go to that page successfully if not so if not success then we want to return a redirect coming from nextjs pointing to forward SL2 fast which is a new route we'll create as a matter of fact let's add this same code over to our signin function as well so right here at the top just below the destructuring of the parameters I'll add this IP address and then we will redirect to to fast if we have made too
182:30 - 183:00 many requests and now we can create a new page for that to fast route by going to app rout and create a new folder and call it to fast and within it create a new page. TSX within which we can run rafc to quickly spin up a new component and right now let's manually head over to it by heading over to Local Host 3000 for slash2 fast you should be able to see just an
183:00 - 183:30 empty page within it let's return a main which is an HTML 5 semantic component and let's give it a class name equal to root- container Flex Min dh- screen so it takes the full height flex-all so the elements appear one below another and items Das Center and let's not forget to justify Center as well within it we can render an H1 that'll say something like
183:30 - 184:00 whoa slow down there speedy of course you can put whatever message You' like right here and let's also give it a class name equal to font db- new text- 5X Cel font Das bold and text- light-1 100 and now we can see this message appear below it we can also render a P tag and here you can put any kind of
184:00 - 184:30 text you'd like I'll copy it from one of the r limit examples I found something like looks like you've been a little too eer we've put a temporary pause on your excitement chill for a bit and try again shortly we can also style it a bit by giving it a class name of margin top 3 Max dw- excl text- Center and text- light 400 oh and the reason why we have this double background here is because I accidentally put it in the root but
184:30 - 185:00 actually it should be going outside because it's a standalone page that doesn't need the roots layout it doesn't need Navar so if I move it outside right here and reload you'll notice that now it looks just like an empty page saying whoo slow down it looks something like this on desktop so now to test whether this is working or not let's head back to the homepage and sign out remember if you head over to rate limit you'll notice that currently it's limiting at five requests per minute maybe we can
185:00 - 185:30 change it to something like one requests per minute so now if you try to sign in for one time in one minute let's try to do that it should let you through and it does and if you log out and try to sign in one more time you'll be redirected to the two- fast page which means that the rate limiting has been successfully implemented keep in mind that this doesn't mean that all of the all of the users around the world
185:30 - 186:00 only one will be able to log in in one minute and the others will be let here no not at all what this means instead is that every single user based on their IP address will get this many tries so now I'll change it back to five in 1 minute which should be more than enough and if I go go back and try to sign in one more time you can see that it works perfectly and there's one more thing I wanted to share with you and that is actually an
186:00 - 186:30 example from up Stash's rate limit JS repo under examples nextjs middleware if you want to rate limit not just server actions but also API routes like maybe making calls to image kit API or any other then you can simply copy and paste this code right here and it'll just work immediately you just need to add this code to the same rate limit file we created pretty amazing right to get this much production ready functionality out of this little code with that in mind we
186:30 - 187:00 looked into just a little bit of what up stach offers to make her apps more production ready later down the line I'll also teach you how to create workflows which will allow us to send notifications to our users when their book borrow request is due and much more like implementing user on on boarding flows which is exactly what we'll do in the next lesson great work so far so far so good we've created a couple of pages like o homepage and even
187:00 - 187:30 profile implemented oth with postgress next o and even rate limited them using up stash but is that all there is to an Enterprise ready application well not really that's more so what you typically see in tutorials within Enterprise level applications many more things happen behind the scenes have you ever tried learning a new language on Dual lingo that little language learning app which gamifies the language learning experience while many think that front end is more important than backend and vice versa what's actually more
187:30 - 188:00 important are your users the product you offer to them and the user experience they can get but your front end backend and the entire app falls down the river if no one actually uses it so the first thing you have to do is make sure that people come back and dualingo that best no this is not a sponsor but I'm so excited that you'll get a chance to replicate functionalities from the best in the industry dualingo tracks if the user has not logged in for a specific number of days and if that happens it
188:00 - 188:30 sends a push notification or an email saying hey it's time to learn and sometimes they make it a bit passive aggressive but it's not just that many ride sharing applications or even food delivery applications like Uber Eats will send you those emails telling you to eat more and order some food heck even e-commerce or course websites will do the same saying that you forgot something in your cart so make sure to buy it I do it as well don't get me wrong but only to get you to learn so
188:30 - 189:00 how do these developers do that well it's using workflows in the code base you have to set up some kind of a workflow to check if the user has left anything in the card or if they have locked in recently or not and then depending on that information you can create certain workflows like for example example if a user has not logged in for 3 days send an email or an SMS notifying them to check out the website or if a user has left something in the card send an email or SMS telling them to rethink their decision to buy but
189:00 - 189:30 workflows can do so many more things let's say in our example a user borrowed a book and is close to the return date we can set up a workflow notifying them to return the book before the due date otherwise they'll be charged and yes this is the same functionality that Netflix and other services use to alert you about annual or automatic charges and that's exactly what you learn today using up stash workflows allowing you to schedule events and Trigger them on specific days or situations and much
189:30 - 190:00 more so head back over to your up stash re platform and navigate over to qash at the top then copy the qash URL and qash token and add them over to your envs and let's not forget to add them to our config they'll also go under up stash but this will be a qash URL equal to process. env. qash URL as well as a qash token which is equal to process. env.
190:00 - 190:30 qore token and as with up stash looks like webstorm doesn't understand what Q stash means so let's go ahead and add it to the dictionary because we'll be using it much more often in the future there we go go so now that is done and we can head up and navigate over to workflows letting you know that upst stash workflows let you write durable reliable and performant serverless functions so
190:30 - 191:00 let's head over to Quick starts for nextjs before we set it up for nextjs I want to head over to just general getting started to tell you a bit more about what we'll actually do and what are the key features of up stash workflows one of the key features is failure resilience which means that if your platform experiences a temporary outage your workflow can pick up right where it left off also you have long running executions which means that when you run complex AI models or video
191:00 - 191:30 processing tools even on serverless platforms with strict time limits they'll be executed properly you can create events with wait or notify mechanisms that wait for external events before proceeding for use cases where you're waiting for a user input like clicking on a confirmation email or even handling asynchronous notifications from external systems you can also schedule jobs at specific intervals with support for Chron Expressions great for recurring tasks like reminders or regular reports and of course there's so
191:30 - 192:00 much more but instead of simply telling you about all these features let's actually explore them in practice by heading over to the nextjs quick start here we can follow the steps to get it up and running first we have to install up stash workflow so back in the code simply add this command and press enter step two is to configure your envs which we have already done and step three is to configure a workflow endpoint a workflow endpoint allows you to define a set of steps that together
192:00 - 192:30 make up a workflow each step contains a piece of business logic that is automatically retried on failure with easy monitoring through the visual dashboard to define a workflow endpoint in nextjs navigate into the nextjs app API directory and create a new folder for example called workflow and then create a new route within it here we have a couple of examples there's a minimal one with the request object and more for now we won't use any of these
192:30 - 193:00 examples instead ight provides a complete example of a customer on boarding flow it registers a new user sends welcome emails and periodically checks and responds to the user activity state so this exact workflow will register a new user send them a welcome email wait for a certain time periodically check the user State and then send appropriate emails based on the user's activity so let's go ahead and copy this example and let's create a new route we can do that within app API
193:00 - 193:30 we can create a new folder called workflows and then within workflows we can create another folder called onboarding and then within onboarding we can create a new route. TS file within which we can paste this code if you scroll down right here you'll see a complete code breakdown so we can actually understand what is happening line by line so let's actually go through it together first we send a
193:30 - 194:00 newly signed up user a welcome email by saying context. run step name is new signup and then the function will simply call the send email function with a message of Welcome to the platform and then here we can get the entire email after that we have the initial waiting period to leave the user time to interact with their platform so for that reason we use context. sleep and here you can Define for how long you want to wait in this case it is 3 days step three is a periodic check so
194:00 - 194:30 we're saying while true which means that we enter an infinite Loop and then periodically which means every month we check the users's engagement with your platform and send appropriate emails so right here we get the user State and then if state is not active we send an email to non-active users but if they are active we send them a newsletter and finally we can go back to sleep for 1 month pretty cool right and then you have these additional functions that we used above that allow you to specify
194:30 - 195:00 what you want to do like send email send an SMS or literally do whatever else we already discussed some of the key features like non-blocking sleep and long running tasks another thing we need to make this happen is to create a workflow client because right now we only have a a workflow endpoint so let's actually head over to lib and then within lib like we have rate limit we can also create a new client for the workflow by calling it workflow. TS and
195:00 - 195:30 within it we can export const workflow client by making it equal to a new workflow client like this and of course we have to import that right here at the top by saying import client as workflow client and this is coming from at upwork flow to it we have to pass the base URL which will be equal to our config coming from at libconfig
195:30 - 196:00 env. upst doq URL and then we also need a token which is going to be a config env. up. qash toen looks like it's complaining a bit about the token saying that it is possibly undefined I think we need to provide exclamation marks at the end letting it know that it'll actually be there always because we know that we have added it to our env. loal there we go with that the error is gone and we
196:00 - 196:30 are now exporting our workflow client so now we have both the endpoint as well as the workflow client and we are ready to run it so let me get back to the next GS quick start and we can continue where we left off which is on step number four which says that up stash workflow is powered by qash and qash needs a publicly accessible URL to run your workflows if you want to you can set it up for your local development as well but in this case we'll just deploy our application to get a live URL here it
196:30 - 197:00 says that we can set up an environment variable but this doesn't need to be set in production so for now we'll skip it and then here it explains how we can set up some of the base URL and then finally triggered the workflow and then we should be able to see it right here within our qash dashboard so let's actually deploy our project to verell First head over to your env. loal and make sure that you have all of the keys right here three from image kit then we have our next public API endpoint which we'll have to change in
197:00 - 197:30 production we have two for databases two for up stash and two for qash oh and this is the OD secret generated by OJs once you make sure that you have all of these you also want to make sure that there's no typescript or ES length errors because some times they might block some builds in this course I try to be super careful not to cause any issues so if you look into most files you'll see that everything is topnotch no errors whatsoever but it is possible
197:30 - 198:00 that some errors sneak through and we don't want those little warnings to block our builds so for that reason we can head over into our next. config.sys configuration options one will be called typescript and it'll be an object containing an ignore build errors is set to True property and then we'll also do another one for ES lent where we'll say ignore during builds and
198:00 - 198:30 we'll set that to true as well just to make sure that everything goes right and now is the time to add it to your GitHub repo so just go ahead and create a new repository I'll say University Library JSM make it public and click create after that you want to follow the steps to push the repo to GitHub so we can do that by saying get in it g add do
198:30 - 199:00 git commit DM initial commit then you want to switch over to the main branch add a remote origin and finally push to origin Main in a matter of seconds you'll you see your code appear right here on GitHub once you do that head over to ver.com and click plus right here and add a new project you should see it right here at the top University Library JSM and just
199:00 - 199:30 click import right here It'll recognize that it's a nextjs project so everything will be done for you one thing that you have to do manually is modify the environment variables so go back to your code and head over into EnV local copy all of the variables and simply paste them right here into the input this will populate all of them and for now we don't have to change anything in here we're going to leave this API endpoint to Local Host 3000 but as soon as our app gets deployed we'll also add another
199:30 - 200:00 production endpoint so for now let's click deploy and let's see what happens it looks like in this case I got an immediate error and this was the issue that I was experiencing with es lint before most likely you don't have this issue and your build should run successfully but on my end I think the quickest fix is to head back over to my projects head over to settings and then head over to right here where we have build and development settings and under install command I can
200:00 - 200:30 override just the regular mpm install and append Legacy perer depths to it that worked in development mode so it might work here as well after that I'll just re-trigger the deployment let's see how that goes it's building and it is past 5 seconds so that's already a good sign let's see where it is in a minute or two and there we go in about a minute our website is deployed so let's head over back to our project
200:30 - 201:00 and click visit right here at the top right this should lead you to your deployed URL now copy that URL and then head over to settings environment variables and add a new environment variable called nextore public underscore prod as in production underscore API underscore endpoint and now you can paste the full URL of your app but of course remove the sign in at the end and save it once you
201:00 - 201:30 save environment variables you'll have to redeploy the app oh and let's not forget to head over to our config because we'll need to use those envs right here as well so alongside the API endpoint I'll also add a prod API endpoint which will be equal to process. env. next public prod API end point which is only accessible in production we'll use that one very soon
201:30 - 202:00 when sending out our workflows with that in mind now that our AB has been deployed and we have all of the necessary production envs we are ready to finalize our workflows and make them send out emails to our newly onboarded users so let's do that in the next lesson you see this workflow endpoint that we created in the last lesson one talking about sending emails when a new user signs up and then we wait three days and then we either start sending them a newsletter or we email the non-active
202:00 - 202:30 users well this is all about sending emails and even though qash allows us to trigger these workflows we have to use another tool to actually send out the emails because it doesn't have to be an email it can can be an SMS it can be an inapp notification it can be anything but in this case I think the most common solution is to send out emails so for that we have to choose an email provider in this course I'll teach you how to use resent it is a new email API for
202:30 - 203:00 developers that a lot of great companies like live blocks and even Warner Brothers use and it is also something that I have personally used on JS mastery. proo platform this is not sponsored just wanted to cover this tool as it's used on many production applications and what see even better is that you can completely write your email templates using react components that is not possible with simpler tools like email JS that I typically teach in my other courses so if you want to have completely custom designs you can achieve that now I do want to quickly
203:00 - 203:30 mention that you can totally use email JS to implement email sending in this application as well and I would even encourage it if you're looking for something simple to just get the job done the reason why I'm providing this as an alternative is because recent will ask you for a live domain through which it wants to send emails and if you don't have any kind of a domain name yet you'll have to get one so I just wanted to provide you with an alternative in case you don't already have a domain name or you don't want to buy a new one for this project but in this case I will
203:30 - 204:00 proceed with recent so click the recent link down in the description to be able to follow along and see exactly what I'm seeing and then click get started next create an account head over to the API Keys section and it It'll ask you to create a new API key you can enter your key name such as University Library give it full permissions and then add it as soon as you do that you'll get your API key which you need to copy and add it to your env. loal so right here I'll do
204:00 - 204:30 something like resend underscore token and make it equal to the one that I just copied by the way I am pretty sure that this is the highest amount of environment variables we've so far had in in a YouTube course so if anything that shows how large the app is once you've created your API key you need to head over to domains and add a new domain now this is the point where you'll need your own custom domain and
204:30 - 205:00 this can be the same domain that you're using for your portfolio so if you've built a portfolio watching any of my previous videos before you most likely already have it but if you don't like in my portfolio videos I would recommend you to buy a cheap domain name from hostinger if you don't have it already for your portfolio it'll be a great way to add a bit more reputation to it and if you do have it you can then use that one or just grab a new one and then swap it over I don't even think you'll need hosting in this case you can just head over to domains and choose the domain name you'd like is your typical domain
205:00 - 205:30 name but Pro and XYZ domains can be picked up at like two bucks for the first year so enter your domain name right here most likely it's going to be something like your first and last name in this case I'll go with Adrien JS mastery and registered for the first year oh I can see honey coupons found but we're no longer using honey after what has happened so I got to quickly uninstall that extension very soon but in this case we don't even need a coupon code so
205:30 - 206:00 just go ahead and click continue and then create your account once you get your domain it'll be registered and then you'll get an email which you need to verify it and then you can copy it head back over to domains and add it but make sure to add something in front of it like hello do and then your domain name.com this will make it into a subdomain which is a recommended practice when adding domains for sending emails typically companies have something like hello or marketing
206:00 - 206:30 or whatever else it is so in this case I'll use hello. Adrian JSM mastery.com and I'll click add then you'll be given a couple of DNS records which you want to copy over to your email provider so let's do that right away by heading over to DNS and then you can add all of these DNS records let's do them one by one I'll first add an MX record with a host or name of send. hello and this specific value priority of 10 and TTL of Auto I think you can leave it as it is and we
206:30 - 207:00 can repeat the procedure with additional records I think the second one is a txt record with a name of send hello and the following value we can do the same thing with the third one which is also a txt with this name name and this value and finally we have one more DeMark record which is also a txt with the name ofor dmark and the following value once you add all of these records you will have to wait some
207:00 - 207:30 time as these records can take up to a couple of hours to fully propagate but typically it happens within minutes so let's click verify DNS records and you can see that for me it still says pending and verifying two have been verified immediately as I was speaking and hopefully the third one will too and there we go all of them have been successfully verified well done now you're ready to start building and sending out emails with this domain now it's pretty amazing that appstash already has an integration with
207:30 - 208:00 resent which allows you to very quickly send emails using resent streamlining email delivery in your applications so let's go ahead and follow it to set up some basic email sending you only need to use publish Json method with the recent provider and then ensure your Q stash token and resent token are set for authentication so let's copy this piece of code and as it says in the body field specify the parameters supported by recent email API such as from to subject and HTML you can also send batch emails
208:00 - 208:30 to multiple people and that's more or less it so let me show you how we can use it together so let's head over to our workflow. TS file the one that we created before where we have set up a workflow client and paste the block of code that we just copied there we get another client which is the up stash qash client so we have both we have both the workflow client as well as the Q stash client and the resent and we set it up right here below
208:30 - 209:00 just to make it make a bit more sense we can rename this Q stash client to Q stash client just to follow a great practice and then we can rename it here and then we can Define it as Q stack client just since we have both the workflow client and the Q stash client in the same page and then it requires us to pass a q stash token so let's do just that saying that the token is equal to config coming from our lip config env.
209:00 - 209:30 up stash do qash token there we go so now we're defining both after that we want to create a functionality to send out an email so let's say export const send email is equal to an async function where we take in some parameters like the email itself the subject and the message and then we can return something and that something will of course be this block of code that we copied from qash documentation with their
209:30 - 210:00 integration with recent we can rename this client to qash client and we have to provide the types for email subject and the message all three of which will be strings so we can say email is a type of string subject is a type of Str string and the message itself is also a type of string great so now we can use those parameters which will pass into the function to compose our email we can do that by saying API name is email with
210:00 - 210:30 a provider as resent and here we have to add the recent token which I believe we have already added to our environment variables so we can say config Dov Dot and this will now be I believe recent let's see have I added it I'll go to config oh and it looks like we're missing the recent token so I'll quickly head back over to recent and go to API keys and try to copy the token from here but it looks like it's not actually allowing me to recopy it so in
210:30 - 211:00 case you didn't pasted before you'll have to remove this API Key by saying delete and then we'll just recreate a new API key called University library and maybe even for the better because now we actually have created a new domain so it can sync the two and now yeah you can only see this key once store it safely so go ahead and copy it and add it right here as recent token as process. env. rendor token and
211:00 - 211:30 then head over to your env. loal oh silly me looks like I added the recent token before to envs but not to the config either way now we have a new token right here so you can simply override the current one and with that back in the workflow we can now say config env. resend token and we can start composing the body of our email so we can say from JSM
211:30 - 212:00 or JS Mastery or you can put your name here and then you can put your current domain name which in my case is hello. Adrian JS mastery.com we're going to deliver it to the email that we pass through pams the subject will be the subject we pass through perams allowing us to modify it and then the HTML will actually be equal to the message that we pass through params as well so it is fully customizable and believe it or not
212:00 - 212:30 that's all there is to this setup so before we go ahead and modify the APA route to send out emails based on conditions we want to have a welcome email when a user first signs up and another email saying something like hey how's it going if the user hasn't been active for the last 3 days so we have to prepare Logic for these conditions like how and when we'll trigger it and if you remember by heading back into schema of our database we have added a last activity date right here which stores
212:30 - 213:00 the last activity date of the user if we keep updating this value whenever our user visits our platform or maybe at least once per day we then know when this user was last active now as a good react developer you might think of using use effect and making a request to update that field as soon as user opens the application or if you're an okay nextjs developer you might want to do it on the server side to avoid writing lots of code and avoid loading on client side but if you're an excellent nextjs
213:00 - 213:30 developer who went through the ultimate nextjs course on JS mastery. proo then you'll want to use the latest nextjs features like next after allowing to schedule work to be executed after a response is finished this is useful for tasks and other side effects that should not block the response such as logging and analytics this way you won't be blocking the UI and you can perform tasks in the background with less code which gives you the best of both worlds perfect use case for this little feature
213:30 - 214:00 so let's head over to our root layout and implement the logic to update the user's last activity I'll do it right here after we make a check if they're signed in and say after here you can define a callback function that happens after the page loads and then within it you can check if there is no session question mark. user question mark. ID you simply exit out of it this is just an additional check even though it might not be necessary because we're already
214:00 - 214:30 redirecting to sign in if the session doesn't exist of course don't forget to actually import after coming from next server and then if a user does exist we simply want to update our database by saying await DB coming from from database drizzle. update users which is the schema for the users and then call the dot set last activity date and set it to New date. 2 ISO string. slice and you want
214:30 - 215:00 to slice from 0 to 10 what the slice does is it simply takes the day month and the year without the time we don't necessarily care about the time in this case and finally we can use the wear method where we check for the equality for the user ID so we only want to update the last activity date for the user that is currently logged in so we'll say EQ user. ID is session user ID like so and
215:00 - 215:30 this was supposed to be users right here and the EQ is of course coming from drizzle orm great believe it or not that's it that's everything we need to periodic update the users's last activity date when they revisit the website now you know that we're all about production ready applications and things that you're not going to see in typical courses so One update that we can do to this is not update it always like every
215:30 - 216:00 time that a user does something and only do it once a day okay so one mutation per day let's do that by writing a comment of something like get the user and see if the last activity date is today and in this case since I'm using copilot or you can use any other AI autofill it should already recommend some things that you can do like const user is equal to await DB where we want
216:00 - 216:30 to select from users so do from users where user ID is equal to session ID and we can even limit it to one by saying Li limit to one pretty cool right and then you can write an if statement and checking if user z. last activity date is triple equal to new date to ISO
216:30 - 217:00 string. slice then simply return meaning exit out of the function and don't perform the update so now this is even more performant but not that it really matters because this after ensures that it never blocks the UI anyway so it will not slow down your application but it will knock down the number of mutation calls to your database so with that in mind let's modify the API workflow route to include these conditions of checking the last activity date and sending emails according to that head over to
217:00 - 217:30 your onboarding route. TS and add the logic for sending emails according to those conditions we can have two types of users type user state will be either nonactive or or it'll be active and then for the initial data in this case we get both the email as well as the full name of a type string we can also Define some constants at the top such as different intervals in
217:30 - 218:00 milliseconds just so we can more easily use them later on without using a programming an pattern of magic numbers like what do these numbers mean right here I don't know it's much easier to Define it as a variable something like one day in Ms milliseconds and then make it equal to 60 * 60 times I think the first number is the number of hours so 24 hours time 60 seconds time 60 minutes
218:00 - 218:30 and then times 1,000 this should be one day in milliseconds we can also create some other intervals like maybe three days Days in milliseconds which is going to be three times and then we can simply use one day in milliseconds much easier right there we go and let's also do 30 days where we can simply say 30 times 1 day in milliseconds after that we want to create a function that will check the last activity State and derive if a user
218:30 - 219:00 is active or not during that period in time so let me say const get user state is equal to an async function that accepts an email of a type string and it'll return a promise that will return a user State it'll look something like this first we have to extract the user which will be the same to what we have done in the root layout get a single user const user is equal to a wait db.
219:00 - 219:30 select from users where users email so here we're going to take a look by email not by ID is equal to the email we're passing right here and we're going to limit it to one next we're going to check if user. length is triple equal to zero that means that it is nonactive it doesn't exist so I'll simply say nonactive but after that we want to make checks for the last activity date so let me say const last activity date is equal to new
219:30 - 220:00 date out of the user z. last activity date and you can add an exclamation mark here letting typescript know that it will be there then with you want to get the current date by saying const now is equal to new date and finally you want to compare the difference by saying con time difference is equal to now. get time
220:00 - 220:30 minus last activity date. get time and then finally if time difference is greater than 3 days in milliseconds and if time difference is lower than or equal to 30 days in milliseconds then you want to return nonactive else you want to return active so this was some pretty custom example of showing you how flexible your workflows and queries are you can do
220:30 - 221:00 whatever you just have to write it in plain JavaScript code and now that we can get the user State we can actually use it to perform some actions in the workflows or use it as a trigger so alongside the email right here in the post request we're also going to get a full name and first we're sending a welcome email so I'll add a comment right here and say welcome email where we run a context new signup await send email and instead of just
221:00 - 221:30 passing different programs we can pass a single object with the settings and then pass over an email we are sending to a subject of something like welcome to the platform and then a message of something like template string of Welcome full name and this send email function will no longer be one provided to us by up stash because it's just a dummy one
221:30 - 222:00 actually will import it right at the top so let's remove this and let's just import it right here coming from lib workflow so this is the custom email function sending using resend as a provider next we want to trigger the work workflows it sleeps for 3 Days to avoid any bills or server load and then it enters the loop here we want to get access to the state of the user so we are running the check user State and then we have an async function which returns an await call to get user state
222:00 - 222:30 to which we have to pass the email so this is the function we created to get the current user State and then according to that we have to send out the emails so let's actually modify them by passing an object into each one for the first one I'll say send it to this email with a subject saying are you still there hey we miss you and then for the second one we'll pass another object and I'll say something like welcome back
222:30 - 223:00 it's good to see you active and that's it then we can sleep the workflow once again for a month and recheck once again so with that in mind we have set up absolutely everything from the workflow client that we have right here under workflow TS lib to the qash client as well the email mechanism to send out emails and the integration with resent and seemingly everything else needed for this to work to test out our workflow we
223:00 - 223:30 can use the trigger method which starts the workflow run and gets its run ID it looks something like this pretty straightforward but the main question is where should we trigger it do you have any guesses where should we trigger the onboarding of the user well of course when a user signs up for the first time so head over to the au. TS file in the lib actions and head over into the sign up function as soon
223:30 - 224:00 as we create or insert a new user into the database we want to trigger the workflow by saying await workflow client. trigger and then you have to pass the URL pointing to the route of the workflow in our case that is the onboarding workflow right here under app API onboarding route. DS which will trigger this post request and with it the workflow itself so we can say URL and then make it a dynamic template
224:00 - 224:30 string of config env. prod API endpoint but first make sure to import config coming from lib config and it should point to your environment variables and then to it append for SL API slw workflow slash onboarding as the second parameter add the body which will contain the email we want to send to as well as the full name and that's it we
224:30 - 225:00 sign the user in and the rest happens behind the scenes so now we ready to test it out but of course since workflows require publicly deployed URLs we have to get our latest environment variables and the latest code and push it to cell so first things first let's get the environment variables we need to get our resent token then head over to your project settings environment variables and add a recent token right
225:00 - 225:30 here and click save alongside that make sure that your next public prod API endpoint points to your currently deployed website copy it and you can also change the next public API endpoint by editing it right here to also point to the same URL no longer do we need to refer to Local Host 3000 on the deployment now to re-trigger these changes you can either go to deployments and then redeploy it manually but we're not going to do that
225:30 - 226:00 because we have to push the changes anyway containing the latest code for all of the logic for sending emails so let's actually just push the code once again which should re-trigger the deployment I'll say get add dot get command DM and that'll be Implement qash workflows for user onboarding and then do get push this will push the latest changes and immediately trigger a redeployment on
226:00 - 226:30 res sell so let's give it a minute there we go our new deployment is ready so let's head over to our deployed website and create a new account in this case I'll use a different email enter my password and my name we can also enter a University ID and upload a university card once you upload it click sign up and there we go we got redirected back to homepage but if you check your email it is nowhere to be found at least I
226:30 - 227:00 can't find it in my inbox so what do you say that we go ahead and debug it together if you head over to qash and move over to workflows you'll see that there is one workflow that started running which is a good sign right and there's another one because one of you was crazy enough to find the published repo and already started testing the application even before it went live or before I finished recording it but here is my attempt of trying to get my email
227:00 - 227:30 JavaScript Mastery right here and it says retrying which means that it wasn't really successful you can also head over to resend and check the emails here it looks like it's empty and the logs are empty too so it looks like it's not Q Stash's fault and it's not resent fault so most likely we've done something wrong so another place to look at for errors is to head over to your server logs on versell so find your versell project head over to logs and then turn
227:30 - 228:00 on the live right here and look for when you made that workflows onboarding call it is right here we have my profile sign in and then we have the API workflow on boarding and in this case it looks like it'll be pretty easy to debug as it's a 404 not found which indicates that the API workflow on boarding cannot be found but how is that the case we have created it well if I go back right here and check out my API workflows onboarding
228:00 - 228:30 route I can notice that it has an extra S at the end it's not workflow it is workflows so I have to go here and search for where we have mentioned that and and I think that's going to be somewhere in the odds of actions so if you scroll down you should be able to see API workflow on boarding but we need to switch this over to workflows on boarding at least that's how I called it so just make sure that matches once you
228:30 - 229:00 do that we'll have to redeploy our app so go ahead and say get add dot get commit dasm fix typo and get push after you do that your app will get redeployed and and you'll be able to sign in with yet another account while it's being deployed we can head over to Neon and head over to tables just to quickly remove the two existing users we have to have a clean slate and to properly on board our new upcoming user there we go
229:00 - 229:30 the application is now live so let's go ahead and check it out I'll log out and create a new account now I can use the email that I used at the start I'll enter some random University ID and upload my University card it should upload it right away if it doesn't try to reload your page and then click sign up immediately you'll be redirected back to the homepage but what we care about more is whether the workflow has been triggered so even before I head over to my email I'll head over to qash workflow
229:30 - 230:00 table and check this out new workflow has been initialized and we have a new signup action which ran successfully and now it's going to sleep for 3 days but if you head over to resent you'll notice that no emails have been sent and also the logs are empty so that means that something went wrong when trying to send out the email even though everything went right with qash workflows so let's actually check out our workflow. TS one more time where we're actually setting up the qash client and allowing it to
230:00 - 230:30 send emails I think we'll need to modify this body from as an example before they provided us with onboarding at recent. deev which is an actual email domain name not just just a website domain I can show you what we had and compare it with this there we go so you can see I have hello. Adrian JSM mastery.com but they have onboarding at recent. deev so what I think we need to do is also get an email account for our domain name
230:30 - 231:00 something like onboarding or contact so I'll head back over to hostinger scroll down and see here it says get a professional email account for your domain name just click try for free and then it'll will be about less than a dollar per month and you even get a free 30-day trial and once again if you really can't put in a credit card or you cannot spend this amount in that case you can check out email JS I have a link pointing to the complete implementation
231:00 - 231:30 within another video right below but in this case I'll start with a free 30-day trial there we go I got it if you don't want to get charged you can cancel it for free within 30 days or you can keep it if you're going to use it for your own portfolio it's always great to have a professional main name and email so I'll head over to manage email and I'll create a new email account email can be something like contact at Adrian gsmaster pro.com and you can choose any password you like and you can skip the second step and now you have your email
231:30 - 232:00 so make sure to copy it go back to your application and replace it right here now we can send emails from a real domain name even though on recent domains we have entered a subdomain as they recommended but I don't think that's going to be a problem if it poses some issues we can very quickly add our main domain right here to allow us to send emails with that said let's go ahead and push this change and test it once again I'll run get add dot get commit
232:00 - 232:30 DM fix email domain and get push back on resell we can wait until it is deployed back on the deployed app I'll try to create a new account I'll use my personal one this time enter my name and my University ID number and the card sometimes it's not uploading on the first try for me but weirdly enough if I reload and enter all my information once
232:30 - 233:00 again and try to reupload one more time then it uploads it immediately I'll see if this issue persists and then we can fix it with that in mind let's click sign up oh and I think I get an error because I entered the same University ID as before so I'll change it it's good to know that our validation is working we get redirected to the homepage looks like a few more people have been triggering these workflows but there's one for my new user that's good to know but now the main question is what
233:00 - 233:30 happens if I head over to resent and would you look at that we got a 403 under logs happening just a minute ago but unfortunately no emails just 403s under logs hopefully the these 43s will now give us a bit more information on what went wrong we got a validation error saying that Adrian jm.com the main is not verified so it looks like I was wrong even though they recommended to have a subdomain there we have to use the main domain for our email so I'll
233:30 - 234:00 just head over to domains and remove the existing domain by saying delete and add a new domain but this time it will not be a subdomain I'll just use my full domain Adrien JSM tree.com and then unfortunately we'll have to go through the process of setting up our DNS records one more time so get back to your domain head over to DNS and let's add a few more records the first one as before is an MX record of send and the
234:00 - 234:30 following value the second one is a txt record with the host name of send and this value after that we have another txt with this name and this value and finally we have another txt of dmark and a value of dmark B none there we go I'm already well versed in adding those DNS records now I'll click verify and hopefully it'll verify them as quickly as it did the last time they're still
234:30 - 235:00 pending now but let's give them a minute two got verified in a second like the last time and hopefully the last one does get verified too there we go it took a couple of seconds but now we're fully verified and hopefully this time emails will send as you can see this took us some time to get everything up and running and set up but that's the difference between a small non-production Ready YouTube tutorial and this which is an Enterprise ready course I actually wanted to guide you
235:00 - 235:30 through the real process of setting up tools that companies and developers use on the daily basis and I wanted to guide you through the errors that I experienced in my process and way of fixing them this time we don't have to redeploy our app we can just log out and try to create another account I'll use one of the last remaining emails I have to test it out enter my password and upload my University card and click sign up there we go now let's go through all the steps one more time like we're firing up a jet
235:30 - 236:00 engine we're going to go through all the steps and make sure everything is running smoothly Q stash run started a few seconds ago recent status 200 less than a minute ago under emails we have our welcome to the platform email and finally in your inbox you should be able to see a message from JS Mastery which should come from your own domain contact Adrian JS mastery.com that should say something simple like welcome JSM even
236:00 - 236:30 though this is a very plain looking email in plain text as I told you recent allows you to craft your emails using react components on recent landing page if you scroll to the section of develop emails using react you can see just what is possible and just how beautiful these emails look like this is something we'll cover in the pro version of this course so if you're already watching within JS mastery. Pro platform keep watching but if you're in YouTube you might want to
236:30 - 237:00 transition over there and get all of the benefits by watching this on the platform directly and I don't have to do any further thinking for all of the upcoming users I didn't share this app anywhere so far but it looks like you guys have been triggering workflows like crazy I can already see like five or more emails triggered just in the last couple of minutes I can't imagine what you'll do with this app once I actually release it on YouTube but the good thing to know is that Q Stash's workflows can handle it all because they're incredibly
237:00 - 237:30 scalable making your app production ready and that's how you do it that's how you implement workflows in your applications you should now be able to do any kind of workflow needed for any kind of application but later on as part of the pro part of this course just for practice I'll teach you how to implement another workflow for reminding your users as to when their book is due to be returned so keep watching so far you've developed a feature packed library application but
237:30 - 238:00 it's not yet a library management system as a matter of fact almost every single Enterprise ready application isn't just a single public facing website like this one typically it consists of multiple applications that work together think of an e-commerce store where people can see and buy the products but that store has another admin interface through which employers can add additional products think of Netflix which is very similar
238:00 - 238:30 to a library application where people can browse different movies but there's a behind the-scenes interface through which those movies can be added by now I'm pretty sure you understand the importance of having a complete admin interface to manage the creation of whatever we're seeing on the public facing website so for this platform we have already built a greatl looking user facing platform where we can show the books and allow users to borrow them but now we'll develop a completely new app
238:30 - 239:00 allowing admins to manage those books which makes it a fully-fledged full stack Library management solution with an admin dashboard first we'll focus on taking a look at all of the users that have been created so far and then creating an interface that will allow us to add new books to the library so I hope you're excited what do you say are you ready to bring this app to another level and go from what we typically do in YouTube tutorials and build something that you don't see every day an admin
239:00 - 239:30 dashboard interface connected with the public facing app so let me teach you how we can approach creating these two separate applications within a single file and folder structure which basically means that we're creating a monol lifee application where everything we need for whatever application we need connected with the main app is consisted right within the same folder so let's create a new folder within which we'll handle all of the admin related stuff such as showing statistics users requests book creation and more we can
239:30 - 240:00 do that right here within the app and then create a new folder and call it admin and then create a new page. DSX and use rafc right within it and what do you think why didn't we use parentheses around the admin to make it a route group well that's because we want to have admin to be as part of the route so we can go to admin and then maybe
240:00 - 240:30 forward slash is just the home or maybe we can go to admin and then dashboard or admin forward SL users that's the reason why I didn't turn the admin folder into a route group rather we want it to be included within the URL path now if you check out the design you'll notice that the admin dashboard is completely different from our user facing app everything from the color which I have purposely changed to light to look similar to most admin dashboards also here we have the sidebar and on the main
240:30 - 241:00 app we had the Navar and then here we have some tables and so on so it is completely different which means that we have to create a completely new layout for the admin folder so let's go ahead and do that by creating a new new file and call it layout. DSX run our afce inside of here and let's quickly spin up a new layout we'll create a main and that main will have a class name equal to flex m-- screen w- full and flex -
241:00 - 241:30 row right within it we'll render a simple sidebar and then we'll have a div with a class name equal to admin-c container and within it we'll render a P tag that for now will'll say header and finally here we can render the children the children are coming through props as with every layout so we can destructure the children right here and say that children are of a type react
241:30 - 242:00 node coming from react and we can also check if there is an active session right here by saying con session is equal to a wait so let's add a sing right here and we can check if o exists or rather whether o returns a valid session if there is no session question mark. user. ID or question mark. ID in that case we can simply redirect to SL
242:00 - 242:30 signin so we can sign that user in and check whether they have admin privileges another thing we need are the Styles so let's create a new folder outside of the app folder and let's call it Styles and within Styles we can create a new admin. CSS style similar to the global. CSS that we had before and then if you head over below this video find the GitHub repo and head over to code
242:30 - 243:00 Snippets to copy you can find this complete admin. CSS file it won't contain a lot of stuff just some helper functions to make it easier to create our sidebar headers and some error handling no logic here just styles to make sure that we can focus on what truly matters so once you get it here make sure to import it right within the layout by saying import at/ Styles SL admin. CSS if you do that you can head back to
243:00 - 243:30 your application and navigate over to localhost 3000 for sadmin and you'll see that your entire application interface will change in an instant now we have a sidebar on the left with a header on the top and then the main page in the middle and of course this layout changes as we expand the screen of course we have to make it a bit more responsive but it'll do the trick for now this is just the layout so let's focus on creating the sidebar first I'll
243:30 - 244:00 head over to the components folder and create a new folder within it which I'll call admin and within the admin folder we can keep all of the components dedicated strictly for the admin app so let's create a new file within it and let's call it sidebar. TSX run RFC within it and then import it right here within the layout by simply saying sidebar and then self close it right here like this and
244:00 - 244:30 now let's develop it together we can do that by heading into the sidebar and starting with a div that's wrapping it let's give it a class name equal to admin Das sidebar Within it create one empty div and within that div create another div with a class name equal to logo within it render a nextjs image with a source of SL ions SL admin logo.svg with an Al tag of
244:30 - 245:00 logo a height of about 37 a width of about 37 as well and you can close it there that should show our admin side logo and you can also render an H1 and say something like bookwise which is the name of our application by implementing the admin sidebar Styles you can notice that the H1 is not present on smaller devices but if I expand it you can see it right here now let's head below the
245:00 - 245:30 div that is wrapping the H1 and let's create another div with a class name equal to margin top of 10 flex flex-all and a gap of five and within here we can map over all of our sidebar elements it should look something like this we need to have the homepage all users all books borrower requests and account requests and then at the bottom we'll show some profile information to do that you need to create some kind of
245:30 - 246:00 an array of links to map over that can look like this an array of objects where you can Define the icon and the text and then you would need to duplicate it many times and then run the dot map on it but we never want to do this right here within this file as it's clattering the jsx view instead you can pull it into constants so let's head over into constants index.ts and here alongside sample books
246:00 - 246:30 right at the top you should also be able to see admin sidebar links allowing us to quickly map over them so let's do just that I'll say admin sidebar links. map where we get each individual link and for each link we can open up a function block first we want to figure out whether this link is currently selected so I'll say is selected and I'll set it equal to false right now then we can return how each one of these links will look like first of all it'll
246:30 - 247:00 be a nextjs link with an href pointing to link. route and its key will also be link. route since it is unique then within that link we can render a div that div will have a class name equal to it'll be a dynamic class name so we can call the CN and first it'll always have a class name of Link but then only if it is
247:00 - 247:30 selected then we will also apply a class name of BG primary admin and Shadow DSM you'll see soon what difference that will make within that div we can have another div that'll have have a class name equal to relative and a size of five this is to wrap our image so let's create an image right here with a source of link. IMG and an I'll tag of Icon a property of fill and
247:30 - 248:00 a class name of now this is a pretty interesting feature in CSS that you can do and that is to invert the color of the image based on a specific proper property making it work on both dark and light backgrounds you can do it like this first check if is selected and if it is selected then change the brightness over to zero and invert the colors else simply apply nothing and always it can have object contain if you
248:00 - 248:30 do that now and properly close it you can see five different icons will appear right here later on as we select them you'll also be able to see that they'll change color it should look something like this typically they're gray but if it's selected they should turn white I remember first using this technique of changing the icons maybe 5 years ago when I was creating the filmire course it was my first react course ever since then I also used it within the ultimate nextjs course and now finally within
248:30 - 249:00 this course as well I used to think we need to have two separate images for different colors but no with a bit of CSS magic you can make it work there we go I fixed the typo and now we can head over below this div and render a P tag that'll simply render the link. text it can have a class name equal to if CN is selected so if is selected then it'll have a text white else it'll have a text
249:00 - 249:30 dark it's pretty easy to work with text right to change colors now if I expand this you'll see that on larger devices there is text now how do we figure out which link is currently Act active when we're just on admin the homepage should be active so we can do that by using the route parameters or the path name so right here at the top of our component we can say const path name is equal to use path name coming from next
249:30 - 250:00 navigation and since we're using path name we have to use browser functionalities which means that we have to make this component client rendered so let's add a use client directive at the top with that in mind we can fix this is selected to false to implement the logic of actually changing it dynamically we can do it like this put parentheses and then check if link. route is not equal to SL admin so this is the
250:00 - 250:30 homepage and if path name do includes link. route and if link. route. length is greater than one exit exit parenthesis and say or if path name is triple equal to link. route in those two cases the route is selected so you can see that currently homepage is selected I'm going to zoom it in a bit so you can see that this icon indeed
250:30 - 251:00 turns white cool and as I Collapse this to mobile we can only see the collapsed icons without the text to conserve some space perfect now let's head down and let's implement the C for the user profile we can do that by going below two of these divs and creating another div with a class name equal to user within it we can create an avatar this is a component that we can import from components UI Avatar and we can refer to how we called
251:00 - 251:30 it previously let me see Avatar it was in the header yep we have to call it like this so let's copy this part and just let's let's call it here again Avatar Avatar foldback get initials and then we have to get access to the session and we can get access to that session by simply passing it over from the layout so here I'll say session is equal to session and moving into the sidebar we
251:30 - 252:00 can destructure it right here and set the type of session to be equal to session coming from next o if you do that you can now see see this little Avatar icon which says a for Adrian and right below it we can also render another div with a class name equal to flex flex-all and on Max MD devices hidden we're hiding it because we only want to show it on desktop
252:00 - 252:30 devices and that something will actually be a P tag that'll render the session question mark. user question mark. name we can duplicate it below and also render the email so that'll look something like this and we can also provide some additional class names like font - semi bold text- dark 200 and for the second one we can give it a class name equal to text- light 500 text- XS
252:30 - 253:00 for extra small there we go this is looking better and as we collapse it to mobile view you can see that now we have only the Avatar perfect and now that we have the sidebar let's Al create a header which is the second and the last component within our layout so I'll head over to the components folder into the admin folder and create a new file called header. TSX and I'll run RFC within it then within the layout we can
253:00 - 253:30 simply import it and use it right here by saying header coming from components admin header because they're different and I'll also pass a session to it because I'm sure will need to use it so now let's get into the header and let's implement it starting with getting access to that session that we're passing over that is of a type session session we can change this over to an HTML 5 semantic header tag as well as
253:30 - 254:00 give it a class name of admin Das header within it we can render a div that'll have an H2 which will render the session question mark. user question mark name and below the H2 we can render a P tag that'll say something like monitor all of your users and books here so let's give them some styles by giving it a class name equal to text- dark 400 and
254:00 - 254:30 font D semi bold as well as text- 2 Excel and let's also give this ptag a class name equal to text- slate d500 and text-base there we go looking good and finally below this div we can render a P tag which for now will'll say search later on we'll convert this into a fully
254:30 - 255:00 fledged functional search but for now we'll leave it as it is or you can even comment it out and with that done we have implemented the complete layout for the admin dashboard of course that is only the layout we are yet to implement the main dashboard page so yep this right here is the main admin dashboard but what matters most is that the main layout is now done so now we can very easily switch between other pages and Implement them first one of
255:00 - 255:30 which will be a page within which we can add new books to the library as admins so let's do that in the next lesson first let's implement the ability for admins to add new books for that we have to implement the all books route so heading back into the code let's head over to our admin and create a new folder called books and within books Let's create a new page. DSX within which we can run
255:30 - 256:00 rafc allowing us to visit this route just to verify it's the one I can change this text over to books and it changes right here so let's turn this into a section and let's give it a CL last name of w fool rounded D2 XL as well as BG white and p7 now we have a card-like layout within it we can create a div and that div will have a class name all Flex
256:00 - 256:30 flex-wrap items Das Center justify Dash between and a gap of two within it we can display an H2 that'll have a class name of text- excl and font D semi Boldt and it'll simply say all books there we go that's better below it we can render a button and that button will simply say plus create a new book let's give the
256:30 - 257:00 button a class name of BG primary dadmin and that button will actually be a link so we can say as child which means that the button will actually become a link href will be SL admin SL books slne and now the link will render the text that the button was before rendering that'll look something like this of course let's style this further by giving it a class name of text- light
257:00 - 257:30 100 or we can do just text white now we can head below this button and below the div and create another div which will wrap our table so we can just do a P tag for the table right now but later on we'll turn it into a fully fletched dashboard worthy table it'll have a class name of margin top of seven to divide it a bit from the top w- full and overflow Dash
257:30 - 258:00 hidden so now this is looking more like an actual page within a dashboard but why do we need the all books page and the table if we don't yet have any books so what do you say that we focus on the more important component first which is the form that allows us to add books to the library and utilize our full permissions as admins of the site we can do that by creating a new page within books or it's going to be a new folder rather called new and within new we can create a new
258:00 - 258:30 page. TSX where we can run our afce so now if you click on create a new book you'll be redirected to that page within it we can return an empty react thre M and within it we can return a button that button will once again render a link with an href all SL admin SL books and a text that says go back so if at any point we want to go back to the old books page now we can do that let's say
258:30 - 259:00 that this button will act as a child as before to make it become a link and give it a class name of back- BTN perfect so now I can go back and we can create a new book to create it though we'll need a new section right here below the button with a class name of w-o and Max dw-2 EXL within it we'll render a new book form that'll be a big form
259:00 - 259:30 component bigger than what we had on registration form to allow us to add all of the important information about a book so before we create that form let's actually head over to validation .ts where we had validations for the signup schema as well as the signin schema now we want to have another one for the book form so let's say export const book schema is equal to z.
259:30 - 260:00 object and then what will a book have well it'll have a title of z. string. trim. min 2. max 100 meaning that it can have no less than two characters and no more than 100 now we can repeat this and render the same thing for the author next let's have a genre so we can have something like genre will be very similar but maybe it cannot have 100
260:00 - 260:30 characters it can be just about 50 a book can have a rating which will be z. number and it can be a Min of one and a Max of five so it's a rating between one and five it can also have a number of total copies so that'll look something like this total copies is z. coer coer means that we want to turn it into a number so we'll say int which means integer do positive so it must be a
260:30 - 261:00 positive integer and LTE which means lower than and then we can put something like 10,000 here let's say that the 10,000 is the upper limit oh and under cores you don't actually have to call it you just say z.c do number and then you keep writing it as you're used to after that we can also have a description which will be similar to the title so let's add it right below it'll be description but let's say that it can be a minimum of 10 characters and a maximum
261:00 - 261:30 of 1,000 characters after that we can have a cover URL so let's say cover URL is z. string and it must not be empty so I'll say nonempty next we need a cover color to be able to choose how the covers of the book will look like so that'll be a z. string. trm and it must be a valid heximal color so here we could write a regular expression that will check for the validity of a hexol code so say do Rex and here it must
261:30 - 262:00 start with a hash and then contain numbers from 0 to 99 and letters from A to F and it must have six characters it's pretty crazy how get copilot fill this out for me it'll also contain a video URL about a book which is of a type z. string. non mty and finally it'll have a summary of the book which is z. string. trim. Min of 10 characters great so now
262:00 - 262:30 we have our book schema and we are ready to create a new form so let's head over to our components head over to admin create a new folder called form forms and within forms create a new file called book form. DSX and within it we'll actually want to implement a regular shat CN form I think we've already done something similar back in our OD form right remember that
262:30 - 263:00 one it's this one right here it's a pretty lengthy one but it has most of the things that we already need for the form so what do you say that we copy it get rid of the things that we don't need and then utilize the fields that we actually need I'll copy the entire thing and paste it into the book form now if I save it we are ready to go back to the page. TSX and import book form right here you'll notice that webstorm will automatically ask me to provide all of these required props that we typically provide to forms but for now I'll skip
263:00 - 263:30 providing any of them because we don't actually have them as this is not an odd form so let's head into the book form and let's get it rid of all of these props as in this case it doesn't have to accept many I'll remove this T extends values in this case we don't have to make it as reusable as we did before it'll only accept a prop of type and we can also spread all of the properties about the book this is if we're editing a book that we already have the information for and this can be of a
263:30 - 264:00 type props so now we can change the props and it'll only accept one prop which is an optional type of create or update date pretty simple and instead of extending field values props will now extend a partial type off book coming from types. D.S we are importing the router that is good in this case we don't have to be considered whether it is sign in or sign up the form
264:00 - 264:30 declaration will look a bit simpler here we can just say const form is equal to use form z. infer type of book schema so so here we're using that validation to make sure that our form is filled in correctly we pass in the resolver with the book schema and let's not forget to import Z from Zod which is actually doing the validation and then here we can provide the default values now for the default values we can grab all of the values from the validation from the
264:30 - 265:00 schema you can do that by holding the option key and then selecting by double clicking all of these fields right here once you do that simply go ahead and paste them here select the end of each one to add multiple cursors and then simply give them their default value for most it'll be an empty string but for some like the rating or the number of total copies it'll actually be set to a number like a one so now we
265:00 - 265:30 have the form set up as well after that we'll have an onsubmit function so let's actually remove the existing handle submit and let's just make something simple like const onsubmit is equal to an asynchronous function that accepts values of a type z. infer type of book schema so what this means is that we'll simply get the values off of the fields of the form and then for now we can simply leave it as an empty callback function and then finally we
265:30 - 266:00 have the return in this case we simply want to have the form nothing else so I will remove everything before the form and everything after the form right here just keeping the form element I'll change the forms handle submit to be equal to onsubmit remove this W full and just make it space Y8 to give it some more space and in this case we won't be mapping over any default values we'll be just rendering different form Fields one by one so the first one will be a form
266:00 - 266:30 field that'll have a name of title and we can remove the rest then it'll render a form item with a class name equal to flex flex-all and the gap of one with a form label of class name text-base font dormal and text- dark 500 and here we don't have to do any kind of magic here mapping over the items in this case we can make it simple so I have shown you both of how we can
266:30 - 267:00 make it super reusable by mapping over all of the fields similarly to what we have done within the OD form and then here we can literally just say hey this is a single form field doing a single thing thing so let me spell it out for you book title after that we have a form control which will render just a simple input so the only thing we can keep is an input which will be required it'll have a placeholder equal to book title we'll spread the field and it'll have a class
267:00 - 267:30 name of book- formore input finally we can exit the form control render the form message remove all of these additional parentheses and move the button that we have at the bottom and this should leave us with a pretty straightforward looking form we did do a lot of the cleanup you can see some values are still unused we'll use them soon and we can definitely remove many of the Imports here as well as here and here bam only now I can see how big and
267:30 - 268:00 reusable the initial odd form really was now that I see how many Imports there are but as you can see now we have a very clean and straightforward not odd form we can actually rename this to book form and we can rename it at the bottom allowing us to create new books so far we only have one single form field which is the book title but we actually need many many more the full form will look something like this
268:00 - 268:30 everything from book title to author genre total number of books book image primary color video and so on so the reason why I didn't just map over all of these these inputs and do it similar to the OD form is because here we have a couple of different types of inputs the first three are regular text inputs the fourth one is a number input then we have two different file uploads one for images and one for videos that'll be exciting then we have a specialized
268:30 - 269:00 hexadecimal input for color selection and finally we have a long text area for the book summary so let's go ahead and code out all of these inputs I think the simplest way to do that is to Simply copy this form field and duplicate it below now if you go back you immediately have two different inputs but this time we'll change the name from title to author we'll also change the form label to say author and then in the input we can just say something like book author there we go as simple as that let's also
269:00 - 269:30 copy it one more time and paste it just below this time we won't be dealing with the author we'll be dealing with the genre of the book so let's say genre or the category and then here we can say book genre there we go now let's go for the fourth one so I'll copy the same input once again but this time we'll have to modify it just a bit this time it'll be rating so we can say rating here as well and we can say book rating and change
269:30 - 270:00 the type over to number okay pretty cool right I think the number also allows us to provide a Min and a Max so if I set Min to one and Max to 5 I actually believe it'll limit it from 1 to 5 there we go this is pretty cool right after that we can copy this input or form field and create another one for the total number of copies so I can say total copies and then say total copies here and
270:00 - 270:30 finally total copies here as well I think the max here is 10,000 so I'll set it to 10,000 and set to Min to be equal to zero or if we're adding a book it must be one right right after that we can duplicate this form field one more time this time we're dealing with a cover URL for this book so we can change the form label to book image and then within the form control for now I'll leave it empty because here we want to have a specialized file upload component
270:30 - 271:00 even though so far we've created an image upload we'll take that image upload and make it more reusable to allow us to upload both images and videos we can also duplicate it one more time this one will deal with the cover color so we can say primary color here as well and instead of rendering a file upload here we'll Implement a Color Picker which we don't yet have so we'll also leave it commented out for now and I'll duplicate it once more this time
271:00 - 271:30 for the description so I'll say description the form label will say book description and within the form control we can render a text area element text area has to be added from shaten and we haven't installed it yet so let's do that now by saying MPX shaten add latest add text area there we go it got added very quickly so let's one more time import it from a proper place it's coming from UI components
271:30 - 272:00 it's a self-closing component that has a placeholder we can say something like book description let's also spread all the field information into it so we can modify the form give it 10 rows because somebody might write a long description and then give it a class name of book- formore input just to make it look a bit nicer finally below it we'll have another file upload so I'll copy the initial cover
272:00 - 272:30 URL and paste it below the book description and this one will say video URL and it'll say book video or we can call it book trailer and here we also have to implement a file upload component finally we'll have a summary a book description is a bit different than a book summary but they're still similar so I'll go ahead and copy the book description form field and paste it
272:30 - 273:00 right here at the bottom and call it summary book summary change the placeholder to book summary maybe make it five rows I think we're good finally what would the form be without a button so right below the last form input let's create a button a button that will say add book to library it'll have a type of submit so it'll submit the full form and a class name equal to book-
273:00 - 273:30 formore BTN so it'll look something like this we can also get it a text- white property there we go so with that in mind we have just built out the entire UI of our form it looks good on mobile but here's how it looks like on desktop of course it could have been further stylized by maybe making the book title take half a row and then the author
273:30 - 274:00 could take another half a row but you know what with these admin dashboard applications design is never what matters you know how I always talk about how UI ux and design really matter because people are visual beings well it does matter on public client facing websites does it matter in an admin dashboard that only people adding those books will see not at all there the most important thing is that it's simple to understand and simple to use so we can very quickly go here and add new books
274:00 - 274:30 in a matter of seconds so with that in mind we now have a nice skeleton of the form but we're still missing some Fields so let's go ahead and add them in the next lesson for admins to be able to add new books to the library they need to be able to upload some files be that a book in a PDF format maybe a cover image of that book or even a video trailer they'll need to upload some files and make them accessible for everyone to see so back
274:30 - 275:00 on the public facing website we can quickly retrieve those files and show them to the users if you remember we already had to do something like that on our signup page where we provided the users with the ability to upload their University ID card and for that we used image kit and created this image upload component now since we'll have to upload both videos and images let's modify that image upload component to handle uploads of both of these file types I'll rename the image upload component to file
275:00 - 275:30 upload and will once again use image Kit's grade feature to allow us to upload those images and now even videos and then later on very easily deliver them to our user to do that first let's accept some more props into our file upload component I'll leave the on file change here but I'll actually copy its type declaration and I'll just change it to props because we'll be adding many more props to it so let me declare a new interface called props right here and Define the on file
275:30 - 276:00 change type but on top of that we want to add many more Fields such as type to allow us to figure out whether we want to accept an image or a video next we have the accept property I'll explain very soon what that does a placeholder also of a type string a folder in which you want to place that file a variant which can be either dark or light and finally the on file change so now let's accept all of these props right here so
276:00 - 276:30 we can later on pass them into this new reusable file upload component I'll be getting a type accept placeholder folder variant and on file change great from before we have this ik upload ref allowing us to upload those files and we have a state allowing us to set that file to the state I'll introduce yet another state right here called progress and set progress at the start
276:30 - 277:00 set to zero image kit will very easily allow us to track the progress of our upload which is super handy for larger files or videos then we can Define some Styles right here at the top so we can very easily use them later on within the code we can Define the styles for the button depending on the variant so if the variant is triple equal to dark that means that we're showing this component on the public facing website not on the admin dashboard in that case we can give it a BG dark of 300 but if we're showing
277:00 - 277:30 it on the admin dashboard we can give it a BG light of 600 border gray of 100 and a border we can also mod modify the color of the placeholder so if the variant is triple equal to dark we can change the text to light 100 else it can be text- slate d500 and finally we can create styles for the text itself where if the
277:30 - 278:00 variant is triple equal to dark in that case we can change the color to light 100 else we can have dark 400 the on eror that's we had here will remain the same but we'll just say not image right here we'll say type upload fail which will look something like this so if it's an image it'll say image upload failed else it'll say video upload failed same thing here I'll just change this over for type we can also
278:00 - 278:30 collapse everything that we don't need so Styles and the on error after that we have the on success where we can do the same thing instead of saying image we can say type upload failed and then we can leave this the same perfect now an additional thing that we can do with image kit is provide validation for the files we're uploading so I'll say const on validate is equal to a function where we accept a file of a type file and then we can check if the type of the file is
278:30 - 279:00 image and if the file size is greater than 20 * 124 * 124 which is is 20 megabytes we can return some kind of a toast where we can say title file size too large upload a file that is less than 20 megabytes in size and a variant of destructive and after the toast we can return false as in we're not going to validate this we're not going to let
279:00 - 279:30 it slide we can exit this if the one that says type is image and add an else if and check if type is triple equal to video if that is the case we want to check if file. size is greater than 50 megabytes so that's 50 * 124 * 124 and we want to do a similar thing saying file size too large upload a file that is less than 50 megabytes in size and once again we want to return false oh and it looks like I added an else on the
279:30 - 280:00 wrong if the else was actually supposed to go under this if so let me actually copy this part of the code and put it one if below there we go this is good so now we have if image and then we have an else if video perfect and if we reach outside of both of these ifs that means that we can say return true the validation is good finally we can now improve our image kit upload to make it more versatile more
280:00 - 280:30 robust and more flexible we'll do that first by defining the ref attached to this I upload after the ref we have an on error and on success we can remove this file name right here and then we have a class name hidden because remember we wanted this component to look nicer so we're actually hiding it here but then we're calling this component on a click of the button that is then more properly styled and now we can use many different props that image kit provides out of the box that give you additional functionality such
280:30 - 281:00 as use unique file name and we can said that to True validate file and here we can call on validate on upload start we can call the set progress and set it to zero on upload progress you get access to a callback function where you can destructure the loaded property as well as the total and then you can return the percentage by calculating it by doing math. round and
281:00 - 281:30 then dividing the loaded by the total and multiplying it by 100 to get a percentage and then you can set progress to be equal to percent so this allows you to very neatly track percent by percent how your upload is going we can also Define to which folder we want to upload this file and we can also pass the accept equal to accept we'll modify this variable to say something like image everything or video everything for now we'll just leave it like this
281:30 - 282:00 Dynamic so whenever we change the type from image to video we can also modify which types of files do we accept great I think that's more or less it for the ik upload and we were using that one on o form right so let's actually go ahead and log out and head over to create an account there we go it's still looking the same we still have to test the functionality though so let's scroll down here we have the upload button but alongside that class name I'll also provide some additional Styles so I'll
282:00 - 282:30 say CN and always it'll have the upload BTN class name but then we'll also provide it styles. button which will change it depending on whether we're on the public facing website or on the admin dashboard then within the button we have the image below it we have a piece of text so let's style it as well by giving it a CN property of text-base and then give it a styles. placeholder and here we can dynamically render a placeholder text because now
282:30 - 283:00 we're making this component reusable and right below this P tag we can check if a file has been uploaded so if file is there then render a P tag that will render under defile. file path and it can have a class name equal to CN of upload file name and a styles of text I noticed that this file upload change color right now and it doesn't look that good also remember how our toast component didn't look that good I
283:00 - 283:30 think that's because when setting up shat CN I actually chose to use CSS variables but I think we can change that if we head over to components. Json file you see CSS variables to set to true we can set that over to false and that'll make it not use the CSS variables which will then fix some overrides in shats and components that will not fix this button though this one is white because we are not yet passing the prop of variant to this component we'll do that very soon but before let's just finalize
283:30 - 284:00 what shows up when we actually upload the file right here below this button we can show the progress yep now we have that too so we can say progress if if it's greater than zero then we can display a div with a class name of w-o rounded Dash fo BG green of 200 within it yet another div with a class name of progress and a style of width and what
284:00 - 284:30 will be as long as the progress is so we can say progress and then percentage so width will be 1% % 2% 3% and it'll go all the way to 100% as the progress reaches the Finish finally within it we can also display a progress percentage now you cannot see it because it's zero but you'll see it soon as we start testing some file uploads and finally here if a file exists we were showing a
284:30 - 285:00 preview of that file but now we can show a different thing depending on the file type so if file exists and if type is triple equal to image in that case render whatever we had here an ik image with a path and Al tag a width and a height but else if type is triple equal to video then render ik video which is a
285:00 - 285:30 self-closing component that accepts a path equal to file. file path controls equal to true as well as a class name equal to h-96 w-o and rounded D excl and finally if it's neither image or video then simply show null there we go are you excited to test it we can first test it within the OD form to make sure that we didn't break anything so let's head over
285:30 - 286:00 to our OD form. DSX and let's properly use this form you can see my webstorm automatically changed the file name for me to file upload you might have needed to do that manually but now it's saying that we need to pass some different kinds of props like type accept placeholder folder variant and more so let's do just that the type in this case will be image because that's what we're uploading we can also say that accept will be equal
286:00 - 286:30 to image SL asterisk meaning any kind of image type placeholder will be equal to upload your ID folder will go into IDs so you can choose where you want to place that image variant will be dark because we're on our public facing website with a dark theme and then on file change is equal to files.on change let's fix this typo and no more warnings and it is looking great matching the
286:30 - 287:00 Dark theme so what do you say that we tested out just before doing that I'll head over to local. drizzle. studio and I'll remove all of the users that are currently here I notice we have a lot of you we testing already sorry about that I'll need to have a clean slate while developing to test everything properly bye-bye now I'll enter my email I'll enter my password as well as a full name and my University ID and most importantly we'll try to upload our University ID card so let me click
287:00 - 287:30 here and select it you can immediately see the progress bar appear but it looks like the file name is too long so head over into your file upload and first of all let's hide the progress if it reaches 100 so if progress is greater than zero and progress is not equal to 100 so if we do that you'll now see that the progress is gone but we also need to remove this name right here so it could be this file path right here I think it's fine if we even just remove that or
287:30 - 288:00 comment it out yep it looks a bit better that way so let me actually remove that and now let's go ahead and click sign up to fully test out our new form there we go it looks like an account is being created oh but we do have a sign of error even though it's hard to spot it with this broken toast it looks like it fails right here where we're trying to trigger the workflow so we'll need this production URL Even in our local enironment because our workflows can still be triggered so let me head over to our env. loal I'll head over to my
288:00 - 288:30 versel environment variables and I'll grab the next public prod API endpoint and just copy it so that'll look something like this next public prod API endpoint is equal to your deployed URL once you do that we should get a 200 but it looks like I was trying to sign up with an already existing user so I'll just use another email right here and then click sign up and I'll have to change my University ID as
288:30 - 289:00 well there we go we have successfully signed up and uploaded our ID let's just see if we can properly retrieve it by finding my user right here and then heading over to the university card and you can see that this even though it seems like it's a URL it's actually a path pointing to where this image is stored within image Kit's file storage so head out to your image kit dashboard and then go over to media library right there you should see a folder called IDs and if you double
289:00 - 289:30 click into it you can see all of the different users identity cards safely stored with all of the corresponding information metadata Tags and More not to mention that we can also edit this image right here with many different editing options and since in our code we are referring to this path right here pointing to this image whatever operations you perform on this image such as remove backgrounds or do anything else the changes will immediately be reflected in the code
289:30 - 290:00 perfect and now that we know that we brought our file upload back to the working State for the O form let's see what it will do for the admin state so if I head over to admin I'll head over to books and create a new book and now we want to use that input here for the image and then right here for the trailer which is going to be a video upload so I'll head over to a form this time a book form and I'll see where we said we want to add an image that is right here file upload for the cover URL
290:00 - 290:30 Yep this this file upload will act as an image upload so we can simply use it like this I'll say file upload imported webstorm will automatically let me know which props I need it'll be a prop of image accept will be of image/ asterisk meaning all image types placeholder will be upload a book cover folder in this time will be books
290:30 - 291:00 slash covers variant will be light because we're on an admin dashboard and on file change we can simply call the field on on change and we can also give it a value equal to field. value and we can self close it right here with that in mind you can see how great it looks like here as we made it completely reusable both in terms of the variant so light and dark mode as well as in terms of the placeholder the icon that we show and more now let's copy it and let's
291:00 - 291:30 move a bit down to where we have the video input or the trailer so I'll put it right here but this time we'll make it upload a video video so we will only accept video files we can say upload a book trailer and it'll go under books videos and the rest can remain the same if you go down you can now see upload a book trailer if I click here you'll see that it'll let me upload a PNG but if I
291:30 - 292:00 click on upload a book trailer you'll see that it won't let me upload a PNG and instead it is expecting a video with that in mind we have now successfully completed the file upload component but just before we test it out we'll also have to implement one more field which is a Color Picker and then we'll be able to test the entire form out so let's do that next to implement our Color Picker component we'll use a very simple package called react colorful which
292:00 - 292:30 spins up this nice component allowing you to select a color Hue as well as opacity so let's go ahead and simply install it by running mpm install react D colorful and once it gets installed now let's copy this block of code and let's create a new component called Color Picker under admin components and then a new file called color picker. DSX there simply paste what you
292:30 - 293:00 copied rename it to Color Picker make sure to export it at the bottom by saying export default Color Picker make sure to import use State as well in this case we'll need the state to manage the color but we'll also open or close this Color Picker so let's also create another used State allowing us to control the is open State at the start set two false when it comes to the props we'll pass into this Color Picker we can
293:00 - 293:30 Define them right here by saying interface of props will accept a value which is the color value of a type string as well as on picker change which will then accept a color of a type string and return nothing meaning void it'll just modify the color so now we can destructure the value and the on picker change and we can set that equal to the props interface now let's try to use it within the book form component
293:30 - 294:00 I'll head down right here to where we're referring to the color and I'll import Color Picker coming from components Color Picker and and onp picker change will be equal to field. on change which is provided to us by react hook form and the value will be equal to field that value which is the currently selected color now if we go back you should be able to see something that looks like this and we could already leave it as it is I mean this is more than good enough provides a lot of flexibility but some
294:00 - 294:30 people might want to specifically choose the color of their book to choose the exact color so for that reason let's head into the color picker let's wrap this return statement in a div with a class name of relative within which we'll have another div with a class name equal to Color Picker now let's put this hex Color Picker right within it there we go this is looking good but what we can do as
294:30 - 295:00 well is add a hex color input as a self-closing component which is also coming from react colorful and we can provide it the color we can provide it the on change which is going to be the same and we can also give it a class name equal to hex Das input and now we can see the live number of this input and as you change it it changes as well now to make it look a bit nicer we can wrap this hex Color Picker within a
295:00 - 295:30 div give it a class name of flex Flex D row and items Das Center we can put it within it and we can also render A P tag in front of it which will simply say hash because all heximal colors start with a hash so that'll look something like this and we can maybe leave this hex Color Picker outside of the div so now we can both type the color as well as select it looking great I was wrong we don't even need this is open we made
295:30 - 296:00 it a bit simpler and we might not even need this state right here of color instead we can automatically use the value that we're passing through props to this component and then on picker change as the on change that way we can automatically change it and you can see that the color changes in real time perfect so that means that the Color Picker is done as well now the last thing is to test the whole UI to see if the data is being logged properly so let's first check if we have any errors or Warnings right
296:00 - 296:30 here it's saying that this value is red because the value does not exist on type file upload oh this is interesting did we forget to accept the value if we head over to file upload it looks like we don't have the value let's add it immediately so value optional of a type string and we can accept it right here through props value and then if a file exists we can use it right here by setting up the initial state of the file so I'll turn it into an object and say
296:30 - 297:00 file path is equal to either a value or null if value doesn't exist and since we're doing this object we also have to define the type and it looks like webstorm did it automatically for me saying that this use state will accept a file path of a type string or null so let's just fix it file path is string or null and we can close it right here perfect now a few of these errors
297:00 - 297:30 are gone we have a few more at the top just unused values and some unused values as well but we should be able to test out all of the inputs so let's head over to the onsubmit and let's actually console log those values right here to see what we're getting back I'll enter some random information for now because we're not yet submitting for real so I'll say test title test author test genre rating can be two copies can be
297:30 - 298:00 10 we need to enter the book image so for now I'll pick one of our default ones that we have on our homepage so let me upload it right here it'll be this one there we go it nicely shows up we can then select the color to match it I think this one is good we can enter a test description and finally we can enter a title this is a real file that we're about to upload typically for videos I use a website called cover with a
298:00 - 298:30 r and they allow you to download some free videos so let's get this one with pouring coffee into a mug it'll very quickly download it and I'll be able to upload it as the trailer of this book A book about coffee so if I select pouring coffee stock video you see the progress bar works it uploaded it we can fix this long name very soon but as soon as it loads it you'll be able to play it right here to see whether this is the trailer you wanted to upload looking good oh and
298:30 - 299:00 silly me I was supposed to be testing this on desktop because that's where most books are going to be added anyway it's not like somebody's going to go on a phone and then add a book there and finally we have a test summary so what will happen if I go ahead and click add book to library let's go to the console clear it and click it it says expected number received string for the rating okay that's interesting let's head over to the rating component which is right here
299:00 - 299:30 we did specify that the type is number here and if we head over into validations and search for rating we can see that it is a z. number oh but what we should have done instead is coerced it into a number right here by saying z. co. number like this so similar to what we have done with total copies so if I do this and try it one more time it's good to know that the validation is working at least I can go ahead and click add book to library and we get
299:30 - 300:00 back a conol log of the entire form we get all the information such as the author cover color oh the cover URL is actually here it got uploaded to image kit which means that it is working and same thing happened to the video URL which is amazing test title test summary rating and total copies as a number and the color right here as a hexadecimal value that means that all of the different inputs that should have been accepting numbers strings images videos
300:00 - 300:30 colors and everything are working properly so now that we know that that is the case we are ready to implement a create book action which will allow us to actually create a book and add it to the database and then later on we'll be able to display it to the public facing Library website so let's do that next to implement a create book action head over to lib create a new folder and
300:30 - 301:00 call it admin within it create another folder called actions and within actions create a new book. TS file as you already know we have to use the use server directive at the top for the server actions to be executed well on the server and we're ready to add a new book to the database we can do that by creating a new function const create book is equal to an async callback function that accepts some programs of a
301:00 - 301:30 type book programs we have to Define these in our Global types and we can open up a try and C catch Block in the catch we get access to the error and if something goes wrong we can simply consol log that error and return some kind of an object that'll say success is false and a message saying an error happened while creating a book okay that was pretty simple so what params do we
301:30 - 302:00 actually need to create a book well let's head over to types. D.S and let's create an interface called book parap and here we can Define different things that we need to pass over such as a title of a type string an author of a type string as well a genre of a type string a rating of a type number a cover URL of a type string a cover color of a type string a description of
302:00 - 302:30 a type string the number of total copies so total copies which is going to be a number video URL which will be a string and a summary which will be a string as well and now that type should automatically be recognized right here at the top next let's actually create a new book and you might think how are we going to do that this is the second time we're doing a creation action within our code we already created users and trust me neon and drizzle make it super simple
302:30 - 303:00 I'll just say const new book is equal to await db. insert we're going to insert the books schema so we're going to get the books and we're going to insert into the books schema so we have to get those books and that means that we have to create a new postgress table so we have to head over to schema. TS and create a new one for the table similarly to what we did with the users let's go ahead and
303:00 - 303:30 do that right below by saying export const books is equal to PG table where we can Define that specific name name of the table which is books and then Define all of the fields that it'll have it'll have an ID of a type uu ID with the name of ID which is not null it is the primary key and by default it'll be random and unique we also need to get a title which is a varar meaning it's just a string of
303:30 - 304:00 Max 255 characters and it must not be null we can do a similar thing with the author by simply saying author is a VAR Char with the name of author that is not null after that we can have a genra which is going to be a text of genre and it must not be null after that we'll have a rating which will be an integer meaning a number called rating and it will not be null so we can say Do not
304:00 - 304:30 null after that we'll have a cover URL which will be of a type text and we have to pass the name of cover uncore URL which will also not be null after that we have a cover color which will be a varar and it'll be called cover uncore color with a max length of seven characters and it must not be null seven because it's six numbers and a hash to create a hexadecimal value then we get a
304:30 - 305:00 description which is a text of description and not null we can also get a number of total copies which is an integer that is is not null and the default value can be set to one after that we have available copies which will be an integer of available copies that is not null and the default will be set to zero finally we have a video URL which will be a text of video URL that is not null we can do a similar thing
305:00 - 305:30 with the summary which will be a varar of summary that is not null and finally a created ad field that'll be of a type timestamp called created ad with time zone true and it'll default to now and this is our book schema now we can head back over to book. DS and insert it into books coming from schema. TS what are we going to insert well we're going to insert the following values we're
305:30 - 306:00 going to Simply spread all the prams coming from a book and we'll set the available copies to be equal to super rams. total copies because we're just creating it for the first time so all the books are available and then we can also say dot returning returning simply means that we want to get that value back that was just created in the database oh and looks like I forgot to import the DB from drizzle there we go so once we created we can just return a
306:00 - 306:30 success of true and we can return the data of json.parse json. stringify just to make sure to properly move it over to our front end from the server action and we're going to return the new book zero because that's the one and only book that was created and let's not forget to add the export right here in front of our server action completing its functionality and allowing us to use it within our form that's it that's all there is to it it
306:30 - 307:00 took us a bit more time because we actually had to create a book schema but in all future mutations it's going to be even easier it is basically a couple of lines of code so let's integrate it within our book form which we completed just recently we'll do it within the onsubmit function I'll say const result is equal to await create book and this is coming from our actions to it we want to pass all of the values from the form see how simple it is once you make the
307:00 - 307:30 form and typescript and Zod and validation and all of these different tools work together you can pass exactly what you get you can get exact actly what you want and everyone's happy so let's check if result is equal to success or result. success is true then we can return a toast which will render a title of success and it'll also render some kind of a description of book created successfully don't forget to
307:30 - 308:00 import the toast at the top and after we show the toast we also want to use the router. push and push to/ admin SL book slash and then result. dat. ID allowing you to see the details of the book that you just published else if result is not success we can simply show a toast that'll render a title of error it'll render some kind of a description of result.
308:00 - 308:30 error and finally the variant will be set to destructive there we go and looks like typescript is complaining bit about this error I think it was result. message there we go that's good so with that in mind the only thing that's remaining for us to do is test the book creation process and to do that I'll open up our public facing website where we have mock data for now and our admin panel to turn that mock data into a real book in the database so let's go ahead
308:30 - 309:00 and copy all the parts from the book title to author you can follow the same thing and then category as well we can do the rating which is 4.6 total copies is 20 looks like I already entered this book title before but you can basically just save image as and then upload it here you can choose the primary color that matches the covers I think it's somewhere near here like a darkish purplish color we have a
309:00 - 309:30 description and then a book trailer can remain this Coffey right here telling us how we can pour ourselves a nice cup of coffee while reading it for now I'll leave the summary to be equal to the description and let's give it a shot if everything works our server action should be triggered and it should create a new book I'll say add book to library oh the rating should actually be an integer this is interesting if you want you can turn it into a float which would allow you to have decimal values but in this case I'll just bring it over to
309:30 - 310:00 five I'm feeling generous today and I'll add it to the library considering we didn't get redirected I'm guessing an error has happened and if you go into our terminal you'll notice an error right here saying neon DP error relation books does not exist and I'm actually super glad that this happened because this is something that happens to everybody and happens often so the sooner you start understanding why this happened and you start putting into practice to automatically generate and
310:00 - 310:30 migrate over your database as soon as you change the schema the better remember how I told you that whenever you change the schema you'll have to then rerun the commands that we added over to our package.json it's generate and migrate so let's do just that I'll go into our new terminal and run mpm run DB generate you can see it provided a new SQL migration and then I'll say mpm run DB migrate we have that warning as before but no errors which means that
310:30 - 311:00 we're good and if you check out our migrations you'll see a second SQL file which simply adds to the first one so it doesn't have to repeat the creation of those tables they're already there but now it created a new table for books and with that the relation books should actually exist and we should be able to create it so let me click add book to library and we get success and even though we're seeing a 404 error here that's good we just got redirected to
311:00 - 311:30 the book Details page which we haven't yet created but that's a good sign so head over either to your drizzle Studio or to Neon tables where you'll be able to see your database and a new table should appear called books where we can see all the information such as the title of Midnight Library Matt who is the author genre rating and more it is all here which means that our book creation process is fully functional but there's one problem with it everybody can head
311:30 - 312:00 over from just a general URL over to admin books new and create a new book that doesn't feel right does it they need to have privileges to be able to create a book so let's head over to app admin layout and let's add a check alongside checking whether a user exists we also need to check whether that currently logged in user is an admin so I'll say const is admin is equal to await DB coming from drizzle. select we
312:00 - 312:30 want to select only the is admin field which is under users. we want to select it from users where we have an equality of users ID which matches the currently logged in user which is the session. user. ID we can limit it to only return one user and we can run a do then on it and check the response and if the response zero
312:30 - 313:00 question mark do is admin is triple equal to admin all operas then that means that we want to return true so by default when you're making an equality comparison the output of that is a bullion so if this matches this function will return true and if this function returns true that true will be transitioned over to this is admin variable so the only thing we have to do is say if not is
313:00 - 313:30 admin then we can redirect over to forward slash so let's test it out reload your page and then if you try to sneak your way over to the admin interface of this great Library management platform you'll Simply Be redirected to the public facing website access denied for now just to allow ourselves the access to the platform you can find your user in the database find the role right here which is currently user and manually switch it
313:30 - 314:00 over to admin if you do that head back to the app and head over to admin you'll see that it'll work because now we have the necessary permissions later on we'll implement the ability to change the user role directly from the admin side but for now this is good too now finally that we can create new books and now that we know that they have been added to the database we are ready to display them on the homepage currently we're displaying mock data here but now we're ready to start fetching real data from the
314:00 - 314:30 database to start displaying real books from our database head over to app rout and then page that DSX with then here we can first get the user session by saying con session is equal to await o coming from OJs then instead of just getting all the users we can just get the latest books by saying something like const latest books is equal to we can put it in parenthesis and say await DB do
314:30 - 315:00 select and now we can select do from books specifically we'll limit it to about let's do 10 books books and let's order it by descending order so we can say DEC which we can import from drizzle orm and then say books. created at just like this and finally we can wrap all of that in parentheses and say as an array of books because that's exactly what we'll be returning now one problem will appear as soon as you do that and that is that
315:00 - 315:30 everything will become red you'll notice that the conversion of type ID title author genre and so on may be mistaken because neither type sufficiently overlap with the others so why is it saying that we have an overlap well that's because before our books looked something like this they had available copies with an underscore total copies with an underscore and they were coming from sample books but now they're coming from a real schema books which is looking something like this even though
315:30 - 316:00 it has underscores in the database when we're trying to fch data using drizzle it automatically converts it over to camel cas so we have to head over into this book type and we have to fix it ID title author genre rating it'll stay the same but it'll say total copies available copies in camel case no longer it'll be a cover now it'll be a cover URL same thing for the video URL and then it'll also have a created
316:00 - 316:30 ad which will be a date or null and we don't need this is loan book so if we do this now the type should be a bit more precise but it still says that it's not a perfect match let's see if we have everything color right here we called cover color so this also has to match the same schema in the database more or less the rest should be okay it says that the only property that is incompatible right now is the ID property which is interesting oh yeah it shouldn't be a number it should indeed be a string thank you typescript for
316:30 - 317:00 saving me here and now you can see the error is gone so finally instead of Simply passing sample books which we no longer need so I can completely remove it from here we'll be passing real books so let's spread dot dot dot latest books zero we'll pass that over to the book overview and let's also pass the user ID equal to session question mark. user question mark. ID and we'll say as string because we know that it'll
317:00 - 317:30 actually exist there and we can also pass those books right here as latest books but we'll run a slice one which means that we'll make the array start from the second element and not the first one because we're already showing the first one in the book overview we don't want to repeat ourselves so let's head into the book list everything should be the same here we're mapping over books and then rendering a book card but we changed the name of some of the properties from the time when we had a sample book and now that we have the real data in the database so let's
317:30 - 318:00 quickly change those as well you can rename the color to cover color and you can use it wh we're using it below you can also do cover that is now a cover URL so let's copy it and paste it wherever we're using the cover that is here and finally is loan book no longer exists but for now I'll leave it as it defaults to false anyway and let's also head back over to the book list and let's hide the book list in case we don't have any books to show so I'll say
318:00 - 318:30 only if books. length is greater than two because the first element actually is in the hero so we're already covering that so only if it's greater than two then show this UL as a matter of fact we should show the entire section only if it has any elements to show so I'll do it right here if book. length is greater than two then we'll return this but if it's lower than two we'll simply exit out of this return meaning show nothing so now if I
318:30 - 319:00 go back we don't see any latest books because the only book we have added to the database is this book right here and it looks looks like it is properly taking all of the information from the book which is amazing but everything besides the C photo so let's head over to our book overview and here we also have to change some naming total copies will be renamed over to Total copies in camel case same thing for available copies color will be
319:00 - 319:30 cover color and cover will be cover URL so let's make sure to change those in the code as well total copies available copies next we have a cover color we have a cover URL for the image and once again we have a cover color and cover URL right here so if we make all the changes properly to work
319:30 - 320:00 with our new database schema now with real data you can see that at least the book gets colored but the image is still not showing we are rendering that image right here as cover URL which we're passing over to book cover and then it's rendering it as an image so let's actually conso log this cover URL to see what we're getting back I'll do a conso log of cover URL and then reload and it looks like we're getting back/ books SL covers and a
320:00 - 320:30 correct webp address but this is not a full address it is a path pointing to image kit our image solution so let's actually head over to the book cover component and instead of just rendering it as a regular image let's actually use the i k image where the path will now be set to cover image URL endpoint will be set to config coming from lib config env.
320:30 - 321:00 imagit URL endpoint Al tag will be book cover we'll have fill class name will be the same we can also use something known as lazy loading so we can say loading is lazy to load other things before it and then with image kit you're also getting a lot of these great functionalities out of the box such as L qip which is lower quality image placeholder which we can set active to true so first it'll render a lower quality placeholder and only then it will render the full image which
321:00 - 321:30 optimizes website performance now image kit has to be used on the client side so for the time being I will change this over to a use client comp component and with that our book is right here it's looking great and now it's coming from the database now in the next lesson I'll show you another production ready technique that allows us to quickly fill up the entire database in this lesson I'll teach you how to seed a database database seedings simply means populating a database with
321:30 - 322:00 an initial set of data it is so common to load seed data such as initial user accounts or dami data in this case our images which allows us to see more data without necessarily needing to manually go ahead and add every single book and since it happens so often within companies and larger applications I wanted to teach you how to do that first head over into constants and then right here find our sample books remove them and instead where you found other Snippets to copy
322:00 - 322:30 you can also find another file called dummy books create a new file in the root of your application and call it dummy books. Json there we go so this is simply a list of all of the dummy data for some programming related books we won't just show this fake data within our application we'll actually use it to populate our database for real so those rows appear there so head over into your database folder and create a new file called
322:30 - 323:00 seed. DS within it let's create a new function const seed is equal to a function which will consol log something like seeding data dot dot dot and we can open up a try and catch Block in the catch we'll simply say something like error seaing data and in the try we'll actually try to do that and if you go back to your application you'll notice that the profile page will broke for a second so for the time being
323:00 - 323:30 we can bring back our old books the sample books that we had here just so we don't break that there we go now in the try let's Loop over our c data by saying for const Book of dummy books so let's try to import those dumy books we can do that right here at the top to get them from our Json dumy books coming from do SL dummy books. Json and we can map over them right here after that before we
323:30 - 324:00 actually insert them into the database we first have to upload the images and cover videos to image kit so let's say const cover UR is equal to a wait upload to image kit and then we can pass a book. coverurl the second parameter can be the title which can be something like book. tile and then an extension of JPEG and finally we can pass the path which will
324:00 - 324:30 be something like for SL books slash covers for this to work we have to do two things the first one is to turn this into an async function and the second one is to create this upload to image kit function so const upload to image kit it'll be an async function that will accept a URL a file name as well as the folder path So within there we can open up another try and catch within the catch you know the deal
324:30 - 325:00 we get the error and then we simply conso log it but in the try we'll try to get the response so const response out of the image upload so let's simply set a new instance of image kit by saying const image kit is equal to New Image kit and then we can pass all of our configuration options such as a public key equal to process. env. imagit public key private key which is equal to
325:00 - 325:30 process. env. imagit private key and then a URL endpoint equal to image kit URL endpoint now only the private key is private but the first two the public key and the URL endpoint are public so we need to add a next public right here in front perfect and now that we have that we can say await image kit do upload and we can pass all these configuration options such as file is
325:30 - 326:00 equal to URL then we pass the file name and finally we pass the folder once we get the response we can just return response. file path perfect so now we can upload images or files and videos to image kit and we actually want to duplicate this and do the same thing with the video so we can say const video URL is equal to upload to image kit book. video URL and we'll upload it to books videos
326:00 - 326:30 and it'll be an mp4 file finally we are ready to insert that into our database by saying await DB coming from drizzle do insert into books the values that are as follows we're going to spread all of the properties of the book and we're going to add a cover URL as well as a video URL perfect we'll also have to make a
326:30 - 327:00 connection to this database one more time we cannot use our previous connection because we will not be running this file from our typical nextjs application rather we'll be running it as a script from a package file like running a separate node file not nextjs so right here at the end of this four I'll add a conso log data seated successfully I'll collapse the function collapse the upload to image kit as well and we have to set up an instance of our
327:00 - 327:30 database by saying config of path isv . local to get access to our environment variables then we can say const SQL is equal to Neon and then to it we pass the process. env. database URL and finally we can export const DB is equal to drizzle to which we pass the client equal to SQL so in this case we're not
327:30 - 328:00 importing the DB from database drizzle we're just creating a new instance of that right here since this file is standal from other files we can also add exclamation marks right here at the end to let it know that we actually know that these keys will be there and we have to import the config coming from EnV and I think that's it the only thing we have to do is call the seed function right here at the end as soon as we call this file oh and I think typescript saved me one more time there's a red squiggly line right here saying property
328:00 - 328:30 cover URL does not exist actually since we're just seating it manually we cannot use the camel case it'll have to be the exact format we're seating the database with so we'll have to use a cover uncore URL here as well as the video uncore URL right here same thing here cover URL and video URL so let's do it properly and we can rename it here as well cover URL now you
328:30 - 329:00 can see here it says did you mean cover URL in camel case so looks like I was wrong we still needed to do everything in camel case as we're just inserting the value using drizzle but in dummy data so far I use snake case so actually I'm interested in seeing whether this will work if not I'll transition it over to camel case so now let me head over to my package.json and the way that you typically seed your databases is by adding a seed command to package Json so
329:00 - 329:30 right here on top of our DB commands I'll add seed and I'll simply make it run that file MPX TSX datab b/ seed. DS so let's head over to our terminal and let's run mpm run seed and I'll expand it so we can see what's happening and press enter it says you need to install the TSX package sure let's go ahead and do that it looks like we got a connection string issue with neon so we have to head back over to the
329:30 - 330:00 seed database and check whether we have properly provided the URL it looks like we are passing it properly but it's like it's not reading it from our environment variables process. env. database URL oh it looks like I have an extra space right here hopefully you had it right so let's go ahead and rerun that command that'll be mpm run seed it's seeding data okay so it looks like it fails after all as you can see
330:00 - 330:30 so far whenever we have been inserting the values into the database we have used this schema right here which has camel case for our properties but then later on in database rows it converts them into snake case so I'm actually very glad that we tested this out because now we know that we cannot insert properties using the final format that they'll be saved within the database with rather we can use camel case so by the time you're watching this video for you I might have provided the right file right off the bat the Json
330:30 - 331:00 file that contained camel case but for myself I will modify it right away there we go I've simply overridden the previous file file with the one with the values in camel case so now I'll just have to make a small modification right here and change these to camel Cas as well you'll have to do that change as well so wherever they're saying video or cover URL simply change it to video and cover URL in commel case but why is there still a red Squigly line right here well because to insert it into the
331:00 - 331:30 database we want to make sure that the value is there so we can say that this will be string and same thing for the video URL as string that way tab script will know that we are properly returning these values so let's go ahead and seat our database one more time with one more lesson learned we're seaing the data this time is taking a bit longer which is a good sign when we're seaing databases as there's a lot of data to
331:30 - 332:00 upload we could have added some additional conso logs to know when the images are uploaded when videos are uploaded and when the books are inserted but as of now we can stare into the screen and look at this blinking cursor and wait for it to say success and there we go data seated successfully head over to your table and just check this out all of these books are right here and if you head over to your local host 3000 check this out we have a book Overview at the top and then
332:00 - 332:30 we have some more books right here at the bottom this is looking great and now you know what it takes to seed any data database to quickly get your project data up and running now that we have all of these great books Let's actually create a Details page for each and every one of them let's go with JavaScript the good parts right now we get a 404 as we don't yet have a book Details page so let's head over back into the code head over into app rout and create a new folder
332:30 - 333:00 called books and within books create a new Dynamic route of ID within square brackets and within it create a new page. DSX there run rafc and first we can accept the ID of the book we're currently on through params so let's destructure the pams and that'll be of a type pams is a promise which in this case will include an ID of a type string
333:00 - 333:30 so let's destructure that ID by saying const ID is equal to await per rams. ID and we can make this function async and now based off of that ID we can fetch the book from our database so I'll write a comment and that'll look like this const book details is equal to DB coming from database drizzle do select. from books. where and now we can use an
333:30 - 334:00 equality sign EQ books. ID and then we match it with the ID we're getting through params do limit to one and I think this is good so let's just await it right here and I think this will return an array so to get the exact book details we can destructure the first parameter out of that array which should bring us back the book details finally if there is no book details we can simply redirect to 404 this is how it's
334:00 - 334:30 going to look like and before we go ahead and return it let's actually just conso log it to see what we're getting back from book details it looks like we're getting back all the correct data so to render the book details I'll simply return an empty react fragment and within it I'll return a component we already created the book overview component you can see we have to pass a lot of props into it or we can simply spread the book details to it we can also pass a user ID equal to session
334:30 - 335:00 question mark. user question mark. ID as string and you already know how we can get this session right at the top by saying session is equal to await o and the O is coming from next o or OJs let's close the book or your component and let's properly import it coming from components if you do that you should be able to see the details of the book you just clicked on so if I head over to react in action you can see the details
335:00 - 335:30 of that book perfect you also have the button to borrow it which will make full use of very soon now right below this primary information we also want to show a video trailer of that book so head just below it and create a new div with a class name of book details within it there's another div with a class name of flex d1.5mm
335:30 - 336:00 there we can render an H3 that'll say video and later here we can display the video component to actually allow people to play that trailer so if we put it side by side you can see how that looks like so right below this section let's also create another section for the book summary it'll have a class name equal to
336:00 - 336:30 margin top of 10 to divide it a bit from the top it'll be a flex container it'll have a flex column so the element appear one below another and a gap of seven there we can render an H3 of summary and within it render a div with a class name of space- y-5 text- extra large and text- light of 100 within it we can render the book details. summary which would look
336:30 - 337:00 something like this but we can split it by new lines by saying do split and then choosing the back slash and character as the separator and then mapping over each individual line which will look something like this we simply want to show A P tag for each one of the Lines within this summary and that'll make it look like this it'll be split into paragraphs looking great and later on beneath this section and beneath one more div we could have a section to
337:00 - 337:30 maybe show similar books which would allow us to Traverse between page details of similar books and finally let's implement this video component which will allow us to play the video trailer of that book so let's create a new component in the components folder and let's call it book video. DSX run rafc right within it and this will actually be super simple thanks to the image kit video
337:30 - 338:00 component first we have to wrap it in an image kit provider which will look something like this to it as you might already know we need to provide a public key equal to config env. imagit dopu key and also the URL endpoint which will be equal to config make sure to import it from our lib config not. EnV config and then say. env. imag kit. URL
338:00 - 338:30 endpoint and within it we can render the ik video component and then pass the necessary props let's first call the book video as a component right here it'll be a book video component as a self-closing component to which we can pass a video URL equal to book details. video URL and then within the component we can get access to that
338:30 - 339:00 video URL right here which will be equal to video URL of a type string and then we can set the path of the video to be equal to video URL if you want to you can set controls to be true or false depending on whether you want to show them and you can provide a class name of w fo and rounded Das excl and let's not forget to turn this into a client component as ik video request us to do
339:00 - 339:30 that and if you do that check this out there is now a video trailer related to this book and if we expand it to look at it in its full Glory this is looking even better a simple and optimized video player component and then a full book summary completing our work on the book Details page finally let's Implement that borrow functionality to start implementing the
339:30 - 340:00 borrow functionality head over to lib and within actions create a new file called book. TS here we can declare that this is a used server file so the code will only be executed on the server and we can create a server action that will initialize the borrowing process so let's say export const borrow book which is equal to an async function which will accept some prams and these will be of a type borrow book
340:00 - 340:30 prams so let's head into the types. D.S and let's define those borrow book perams we can do that simply by saying interface borrow book perams and it'll be just a book ID because we need to know which book to borrow and then finally the user ID to know who is borrowing the book finally we can destructure those two params by saying const user ID as well as the book ID are
340:30 - 341:00 coming from pams and we can open up a try and catch block in the catch you already know the deal we get the error and then we can simply consol log it as well as return a success of false and an error something like an error occurred while borrowing the book and then we can focus on the tribe part here we can make a new mutation to the database by saying const
341:00 - 341:30 book is equal to await DB coming from drizzle do select and and we only want to get access to the number of available copies and that's coming from books. available copies then we want to make sure that we're getting it from the books schema or books table where we have an equation sign books. ID is equal to the book ID that we're currently trying to borrow and
341:30 - 342:00 we're going to limit it to only one book that should look something like this after that we first of all need to check whether that book even exists the book that we want to borrow so if no book. length or if book zero do available copies is lower than or equal to zero in that case we're going to return a success of false as well as an
342:00 - 342:30 error of something like let's do book is not available for borrowing if we can borrow the book though we need to get its due date as to when we need to return it so let's say const due date is equal to and here we can use the DJs Library which helps us deal with dates a bit more easily so let's just install it by running mpm install DJs and then we can say djs. add 7 Day 7 days to
342:30 - 343:00 date do to dat string so we're simply going to add 7 days from today that's the deadline later on we can make this Dynamic by passing a dynamic return value right here instead of the number seven and then we are ready to insert a new record a new borrowing record we can do that by saying db. insert and specifically we want to insert something into the
343:00 - 343:30 database but into a completely new table called borrow records so think of this as a huge library of different records that keep track of which users borrowed which books you don't want to Simply mutate the book itself you want to create a new record that connects a specific user with a specific book so to do that we have to create a new table so let's head into schemas and Below books we'll create another table now since it'll be very
343:30 - 344:00 similar to the tables we created so far I'll provide you with a code for this one you can just find it where you found all other Snippets code and then paste it right here you'll notice that it has the ID the user ID who is borrowing the book the book ID so we know which book is being borrowed the borrow date the due date and finally we'll later on mutate it with the return date so we know what is the status of that return as you know whenever we change the schema we have to rerun mpm run DP
344:00 - 344:30 generate and then once again rerun mpm run DB migrate once you do that we can now insert this right here here by saying DB insert into borrow records and specifically we want to insert the following values user ID book ID due date and Status which by default will be set to Borrowed great and then finally once we create this new record we want
344:30 - 345:00 to mutate the original books by saying await db. update books and we want to set the number of available copies to be equal to book zero do available copies minus one because somebody just borrowed one instance of that book and we want to do it where EQ books ID is equal to the current book ID we're
345:00 - 345:30 on beautiful finally let's return it by saying success is true and and as the data we can do json.parse json. stringify and we can return that borrow record right within so now let's actually go ahead and call this borrow book action when we click this borrow button right there I'll head over into the book overview as I think that's where the button is yep here it is but since this click will now involve client side
345:30 - 346:00 actions such as clicking on a click Handler we'll have to either turn the entire book a component into a client component or create a separate client side component just for the borrow button for better SEO and to get more code generated on the server itself we'll create a separate component for the button so copy this button right here create a new component and call it borrow book. DSX run rafc and paste the button code right
346:00 - 346:30 here make sure all the Imports are in order and then simply import the button where it used to be right here I think it was below the description so that's going to be borrow book and if you save it here we have it but now we can turn it into a client component while keeping everything else server rendered and this is something that I dive into much more detail within the ultimate nextjs course so if you want to learn a bit more about server
346:30 - 347:00 and client components server actions and how all of that works behind the scenes definitely check out that course on JS mastery. proo but with that in mind we'll have to pass some props into this button the first one will be just the book ID so we can say book ID is equal to book ID and the second one will be equal to the user ID which is equal to user ID and I think book ID actually is just the ID because we're already on the book Details page so let's get those values
347:00 - 347:30 we can get them right here at the top of the book overview and I think we're already passing it through the props into the book overview if I'm not mistaken so we should be able to just destructure them right here that'll be ID as well as user ID and we need to also let typescript know that alongside the book properties will'll also be accepting a user ID we can do that by saying interface props extends book with the user ID of a type string so now we can make the type of props and it'll
347:30 - 348:00 know that all of this is a book and then it also has a user ID great now if I remember correctly our users currently also have a status and a status can be pending or approved so far we don't yet have the functionality on the admin side to approve certain users but that's definitely something we can do later so for example a book clerk who is sitting in a library sees your ID checks whether you're from the University that this library is from and then if that is the case they can approve you else they can
348:00 - 348:30 reject you so for now I'll manually approve my user right here and let's fetch our user data by saying const destructure the first user and make it equal to the call to the database by saying await DB from database drizzle let's make it into an async function and we're going to do DB do select. from users. where EQ users. ID
348:30 - 349:00 is equal to the user ID and liit to one now we get the user if a user doesn't exist that means that we can't borrow so I'll say if no user then simply return null and then finally we can figure out can user borrow this is going to be a Boolean variable and then we can create a new object called borrowing eligibility which will be equal to an
349:00 - 349:30 object that'll have an is eligible Boolean variable and we can check the status of that variable by checking checking if available copies is greater than zero and if user. status is triple equal to approved only in that case they should be eligible to borrow it and if not we can render a message saying available copies is lower than or equal to zero in that case we can render something like an error message saying
349:30 - 350:00 book is not available else we can say something like you're not eligible to borrow this book per perfect and now we can pass that object into the borrow book button so I'll do it here borrowing eligibility is equal to borrowing eligibility so let's head into the borrow book and let's get access to all of these props that'll be user ID book ID and borrowing eligibility and that'll be of a type props so let's define those
350:00 - 350:30 props at the top by saying interface props is equal to and then we can say that it has a user ID of a typee string book ID of a type string and borrowing eligibility which is an object with a Boolean and a string within here we'll need to get access to the router properties to be able to renate the user to another page after the borrow so we can say router is equal to use router coming from next navigation and we'll also need to create a new use state so
350:30 - 351:00 we can handle the loading state of the borrow function like borrowing and and set borrowing at the start set defaults and finally we can implement the const handle borrow which is equal to an Asing function that'll be executed once we actually click on that button there we can first check if no borrowing eligibility do is eligible what we can do actually is maybe destructure the is eligible and
351:00 - 351:30 the message from that so we can just do a colum and then D structure them is eligible and message and then we can simply say if not is eligible a bit simpler to do it that way then we can render a toast with a title of error a description of message and a variant of destructive and that'll look something like this if we pass that if statement that means that we're eligible so then I
351:30 - 352:00 can set borrowing to true so we can start the loading and open up a new try and catch block in the catch we get an error and we'll render a toast that will say something like title error description an error occurred while borrowing the book and a variant of destructive we can also add a finally clause in this case because no matter whether it succeeds or fails we'll set borrowing back to false because we want to stop the loading and finally in the
352:00 - 352:30 try the only thing we have to do is get a result from the server action we create created so we can await borrow book server action to which we can then pass an object of book ID and user ID which is exactly what it accepts then if a result is a success so if result success then we show a toast of title success description book borrowed successfully
352:30 - 353:00 and we use the router. push to push to the profile page so my profile to see the book we just borrowed else will show a toast of title error the description can be something like an error curred while borrowing the book and we're not going to Route anywhere finally on click of this button we can say on click is equal to handle borrow book that is this action right
353:00 - 353:30 here also we can set a disabled State and if we're currently borrowing it'll be disabled and we can change the text depending on the borrowing status so if we're borrowing then we'll say borrowing do do dot else we'll say something like borrow book there we go you can see that changed and we are already to test it out I'll open up my neon DB table right here on the left while I click the button and in terminal we get some kind of an other error so at
353:30 - 354:00 least now we know that the book eligibility test is good and if you're not eligible you'll not be able to borrow it but wait I actually thought we approved ourselves looks like I forgot to click the enter or click save changes so now I approved myself for real let's see if I can borrow the book now I click borrow book it says borrowing and now we didn't get that flash that said you're not eligible but we just got an error saying an error occurred while borrowing the book and we
354:00 - 354:30 get that same converting circular structure object okay so now we know that it's not about the eligibility but something else and for the time being we can just remove these fake dummy books from the profile page and if you remove that the only thing that'll remain on the profile page is the logout button so let's actually move that right here to the top right we have enough space there we can do that by copying these form right here that is within my profile
354:30 - 355:00 page and then head over into the header in the header you can comment out the link L right here and instead of the link you can simply render that form you'll need to import all of the things from here such as sign out from o and so on and we'll no longer need to get the session because we don't need to show the username there so we can remove these the initials and more and if we do that we can turn this
355:00 - 355:30 into a server component so let's actually remove the use client directive at the top and remove this first link pointing to the library we can already see a lot of the books on the homepage so if we do that we have successfully turned this into a server component and now the logout button is at the top perfect so now we have a nice looking homepage we have all of the functionalities that happen in the back end when a book is borrowed we create a borrow record and
355:30 - 356:00 more now now seemingly this is a simple application at least on the front end but you know how much stuff is happening on the back and side we're handling the users we're handling the addition of books we're tracking borrow statuses borrow requests and more and in this one I really wanted to go all out and Implement all of these crazy features but doing that is taking so much time and I think this YouTube video
356:00 - 356:30 is already a bit too long so I want to continue developing this app even further Implement a workflow that'll update the user to let them know when their book is due manage user approvals and book statuses from the admin side and more so let's keep this app at the current state as it is and let's get it deployed and then after that we'll plan a way to improve it even further the only thing we need to do to deploy it is push the GitHub so I'll run
356:30 - 357:00 git add dogit commit DM we can say Implement board records and finish the app and then get push this will automatically push it to your repo and since we already deployed our app before if you go to deployments you should be able to see that your app is building right now so let's wait until it finishes and then we can go ahead and check it out live and deployed there we go the status is ready
357:00 - 357:30 so let's go ahead and visit it and we are in but it looks looks like we had an issue with a check for displaying the book overview in the code we're looking if we can fetch a user in the book overview code we're ret turning null for this entire component if we can't fetch a user but that shouldn't really be the case if we can fetch the user then we simply cannot figure out the borrowing eligibility so hiding the borrowing button makes more sense than hiding the
357:30 - 358:00 whole thing right so right here I'll say if no user then simply don't show the book or in other words if a user exists then show the borrow book and we can remove this check right here so let's go ahead and push it one more time by running get add dot get commit Dash fix if check and get push and very soon we'll be able to
358:00 - 358:30 recheck it it build successfully so I'll reload and there we go the book is back just to check out the login functionality one more time you can try the sign up but in this case I'll try just the sign in success we're logged in and this time we have the borrow book as well and if you click it success book borrowed successfully oh but we still have that old redirect to the profile page so if you just bear with me and you want to do one more quick push you can
358:30 - 359:00 head over to where we actually borrow the book so that is the borrow book button and the only thing we have to do is just push it to the homepage once we borrow instead of pushing to the profile so if you fix this you can do another G add. get commit DM fix redirect and get push this is typically how it is when you're pushing changes and you want to see them live in production so with that in mind the app is done and there you have it
359:00 - 359:30 you just built a production grade Library system with features that most tutorials wouldn't even dare to touch rate limiting DDOS protection automated workflows with emails and complex database queries you've learned to implement it all but here's the thing everything you build today it's just the beginning remember those Advanced features I showed you in the demo like the analytics dashboard and the admin
359:30 - 360:00 panel with different tables and even automated workflow CL with performance optimizations yeah those aren't just for the show they're complete systems that showcase the functionalities of real modern web applications now you could absolutely take what you learned so far and build those features yourself that's the beauty of understanding The Core Concepts you have the foundation to explore and create and heck you even have the complete codebase with all of
360:00 - 360:30 those changes already implemented on GitHub so you can check it out and give it a spin but if you want to skip trial and error and dive straight into the advanced patterns I prepared a second part of this course the pro version where we'll build the entire platform together and even more features that would make senior developers not in approval the platform includes deep dives into advanced react patterns step-by-step commits so you never get lost real world challenges to test your
360:30 - 361:00 skills progress tracking to keep you moving forward and community of developers to build amazing things together and as I said at the start there's a link to join the pre-launch of the course down in the description the first 100 developers who join will get a lifetime access completely for free and even if you miss it once I actually release it I'll reach out to you via email with a special pre-launch discount the link is in the description
361:00 - 361:30 but no pressure though you came to the end of this video and that's amazing the proon content is there if you want to take your skills to the senior level once again thank you so much for coding along and I'll see you in the next one or maybe inside JS mastery. proo keep building things that matter and have a wonderful day