Transcripts
1. Welcome To The Class!: [MUSIC] Vue.js is an amazing and easy-to-use
front-end framework for building web applications. In this course, we're
going to use it to build a pizza restaurant application
packed with features. We will discover all
the new features of Vue Version 3 while
building this project. In addition, we'll link up
our project to Firebase, to apply a back-end to our app. This gives us a real-time
database to store our users, our orders, and
pizzas from the menu. It will also be used to provide user accounts and
authentication, including admin
and regular users. The project has many features such as a menu and a basket, add items too and the
ability to place orders. We have an admin area
where authorized users can add and remove
pizzas from the menu. Your orders can be
displayed and also deleted, along with the ability to set
other stuff as admins do. All of this data is
driven from our database. Hi. I'm Chris and I'm an
experienced web developer. I've also taught over
100,000 students, both online and
also in person too. If you want to build amazing front-end applications
with Vue Version 3 and also make use of the Firebase Database
and Authentication, then this is the class for you. I look forward to seeing
you in the first lesson.
2. What You Will Need: For this cluster there's
no additional purchases which you would need to make. All of these software
which we are going to be using will be free
and open source. Then there's only in fact a few things which we're
going to need. But before we do though, let's now take a look at the requirements so we
don't get too overwhelmed. Since this is a web
development class, you should at least
know the basics and it will be
assumed you know HTML and CSS pretty well and also comfortable
using JavaScript. You don't need to be an expert, but you at least need
to know the basics. I do have classes
available on each of these if you need
to brush up first. Also, if you have experience of other JavaScript frameworks or libraries such as
Angular or React, or even Vue Version one or 2, this will be a great
benefit, but not essential. Also, we'll be making
minor use of the terminal, but if you've not used this in the past, don't worry about it. We're only going to make use
of a few simple commands. Visual Studio Code, it'll be the text editor
which I'm going to use throughout this course. It's currently very popular
in the web development world, but you can use any other
editor which you prefer. If you need it, the download
link is here to follow, and it's also completely free. Another benefit is it comes
with a built-in terminal, which we'll use to
set up our project and install the packages
which you may need. But also if you are comfortable
with the terminal and you maybe have your own
external ones such as Item, this is completely
fine to use too. With this in mind, let's move on to the next video where we'll take a look at how we can set
up our project using Vite.
3. Build Tool Setup With Vite: To begin our project, we'll start off
with a local setup. We can run Vue.js on
our own computer. For this, we'll make use of
a build tool called Vite. Vite was in fact created by Evan You who also created Vue.js. This build tool will
allow us to make use of view single file components, which is when we write a part of our website called the
components into a single file. This will keep things
nice and organized for us when we're
creating our project. But it will also means we need a build tool to take all of these separate pieces
of components and bundle them together
to make our app. Vites also gives us
many other benefits, such as a development server, hot module replacement,
which will instantly update the project in the browser without needing to
refresh the page, we will make a change. There's also many
other advantages too. You will need to install Node.js If you've
not already done so. You can do this from nodejs.org. Go ahead and download
the latest version for your operating system. I'm using a Mac book if
you are using Windows, it should automatically
detect this to. If not, just click on
the other download. This download will
also include npm, which we'll use to set up our project and also add
packages as we need them. Next, over to the terminal, which you can use a standalone
terminal if you prefer. Or you can use the one built
into Visual Studio Code. The simplicity, this is
the one which I will use. Go into Visual Studio code, into the options, terminal, and then new terminal. We'll now have this
terminal at the bottom, which we can use
to write commands. If you're new to the terminal, it's nothing to worry
about we just need a few simple commands. You can use a
terminal to navigate your computer's
files and folders, just like you can do
with the mouse by clicking inside of a window. Here though, we use
commands but we'll need to navigate into where we
want to add our project. I want to keep it
simple and add it to the desktop for easy access. Currently I'm in the home
directory for my user. We can use the LS
command to list all the files and folders
inside this directory. Then we can use
the cd command to change into any of these
directories which you want to. For me it's going
to the desktop, which we can see listed here. cd to change in it
to the desktop. This is also case-sensitive. Hit's "Enter". We
can now see we're inside of the Desktop directory. But if you want to,
you can change into any other directory
which you want to. But just before we
create the project, we need to double-check
that we have nodes and npm
correctly installed. To do this, we'll
type in node dash v, which stands for version. We'll see returned back the version number
which is installed. My current version is Number 18. You will need at least node
Version 15 to continue. Obviously, any version number above 15, you'll be good to go. But if you see an error
message or a warning, you will need to go
back and makes sure node is correctly installed. Next, we can double-check npm, so npm dash v. We don't
see any issues here. To create a view JS
project using Vite, we need to run it this command. This is npm in it. View. At latest. Its enter. It's asking us to install the
following packages, which is Create View. This is fine. Press
"Y" or hit "Enter." Then we asked a
series of questions. First of all, the project name, mine will be pizza planet. You can change this
if we want to. We're not going to make
use of TypeScript. We'll hit No, we don't
need JSX support, so we'll go for, No. Do you want to add
the view router? Yes, we do. We'll take
a look at this soon. Do want state management, we're going to select No since we use a state management inside
of composable files. We'll also take a
look at how to do this throughout the class. We won't be covering intestine, so we'll hit No and No. Yes. Length. That can
be yes. It's up to you. Prettier? Yes. Good, and we are now all done. Remember, that earlier we use the cd command to change into the desktop directory
well now we've created a new directory
called pizza planet, and this is now inside
of our desktop. What we need to do,
as it says here, we now need to change into
the pizza planet directory, so cd, pizza dash planet or the project name
of your choice. We then need to run npm install, which will install
all the packages we need for our Vue projects. Good, the packages are
now all installed. To start up our
development server, we need to run npm,
run dev. It's Enter. This will then give
us a web address to open up inside the browser. We can copy this or
Command or Control click, which will open this up
inside of the browser. This is our project starter, meaning everything is
working correctly. Since we set up the view router, which we're going to talk
about a little bit more later. We'll also see a Home and an About link
to switch between.
4. Project Images: Throughout this
project, we will be using some project images. You can download your
own if you prefer, or you can use the exact
same ones which I'm using. If you want to
follow along and use the same ones, inside of GitHub, I have this repository
which is available at github.com/, my username, which is
chrisdixon161/pizza-planet-v3-images with a dash in between each word. This is a simple repository. All we have inside is
this images folder with the six images which we're going to be adding to our project. If you want to use these, just click on the Code button. Download the zip file. Once this is finished,
you can click on this to open this up. Then we need to add
this to our project. First of all, go over to Visual Studio Code
or your text editor. What we need to do is to open up our project inside of here. Drag over the project folder which we created in
the previous video. Mine is stored on the desktop. Drag this into
Visual Studio Code. We now see all of the
files and folders for our V project
inside the sidebar. We may also need to
reopen the terminal. The Terminal and then go to New. We are currently inside of
a pizza-planet project. We can run npm, run dev, which will again open
this up with this link. Command or control click. Good. Everything is fine. Then if we go into our images, and then into our
project folder, which we just opened
up in Visual Studio, go into the source folder, into the assets, and
then we can drag the images folder
inside of here. Hold this down. We can also see these in a sidebar
if we open up the source, into the assets, and there's our images which
we're going to be using for this project. Again, if you want to
use your own images, you can add these to an
images folder inside of here and use these as we
progress through the project. But now with these
available to use, we're going to move on
to the routing section, where we'll take
a look at how we can switch between pages.
5. Views Or Components?: In this section
we'll take a look at some basic routing to allow us to switch between
different pages inside of our application. But just before we do
this, so I want to take a quick look at two different
files which we'll be using. These are views and components. Let's see what the difference
is between these two. First of all, we have
the idea of views. These are files which
hold the contents of each page of view. A typical app or website
would use links such as Home, About Us, and Contact Us and each one of these
will link to a new page. This is how we will
make use of views. Next onto components, and components are generally smaller pieces of
our application, which can be either a
stand-alone item or it can be reused across multiple
pages of views. A component could
be any block of content such as a location map, a product on an
e-commerce store, or even just a simple
block of text. Components can also be reused either by repeating
the contents over multiple pages or passing
a component dynamic data. Component files and view
files are the same. They also have the
same contents and structure since they are
both view components. But having them in
different folders keeps things organized. To recap, a view is a page which we can use to switch
between on our application. Generally, a component
is a smaller, often more reusable
piece of our page. We generally store these in
their own folders called views and components to
make these more organized. This is what we are going to
be taking a look at next.
6. Views & Routes: Inside the browser, we
have our project open. We've briefly
looked at this Home and the About links
which are provided. We switch between
these two pages or these two views which
we've just learned about. We can see these two view
files inside of the Editor. Jumping into Visual Studio
code with the project open inside the source folder where we'll write
all of our code. We have this folder
called views. This has the About and
the HomeView which we just switch between
inside the project. Inside of these files
will look pretty familiar if you've used
Vue.js in the past. They're just component files where we have the
script section, where we write all of
our JavaScript code and import any files
which you may need. Then also a template
area just here. This is the area where
we write our HTML. We can also have a
style section which we can see if we go
to the AboutView. Above this inside of
the source folder, we have this router folder
with an index.js page. We have this since
we chose to install the Vue router package when
we first set up this project. The key here is at the
top of the file where we import any one of our views. Here we can see we are importing the HomeView from
the views folder. Then below inside
of createRouter will have this routes array. Each one of these routes is
an object as we see here. We see the file path,
which is forward slash. This is the home directory. We can give this router name, which we will refer to
during our project. Then link this to a
particular component of view which we just imported. After this, we also
have the About section. You may be wondering
why we've not imported the AboutView up at the top
alongside the HomeView. Well, often we would do this and also important
the AboutView, just like we did with
the Home components. But instead, this
is demonstrating a slightly different
approach where we use something
called lazy loading. We lazy load the page
to only download the page contents when the
user actually needs them. This router page overview, let's now create the views
which we need for our project then we can come back and
add this to our array. Into the source, into
the views folder, we can still make
use of the HomeView and the AboutView
for our projects. Let's just clear
out the contents inside so we don't need
the welcome components. Since we've removed
this, we can also remove the reference
to it just here. Instead of placing a Level 3
heading maybe text a home. We'll come back and add content to these views but for now, we can use the Level 3 heading to see which page we're
switching between. The AboutView, remove the style. We don't have a script section. We have the wrapper, H3, and the text of about. Inside the views, we
need two more files. Click on the "File" icon
just about the top. We also need the AdminView, which uses the.vue extension. Create a template, a heading. This is for the Admin. We file the final view
which we're going to be using is the MenuView. This one will be used to display all the pizzas
which we have from our database and also
a shopping basket too. Another template,
the heading of menu. Then we can close
these down and import these inside of our router, so the router index. These are all in the same
directory as a HomeView. Let's copy and paste
this three more times. The Menu and the MenuView. The next one is the Admin from the AdminView and the
last one is the About. Since we've imported these
at the top of our file, we can basically duplicate
the Home section. Just below this, create a new object separate
it by a comma. The path which we want to use is a string and this is going
to be forward slash menu. This means if we go into our
project and at the very end, typing forward slash menu, we'll see the menu view
inside of the browser. Next, the name, which is the menu link. We'll use this soon
in our project. The component which we want to display is the menu which
we imported just above. Next, we have the Admin, write new object and
separate it with a comma. This one wants to access inside the browser with
forward slash admin, the name of admin link
and the admin components. Good. The last one
we need is About. The path of forward
slash about and the order which we place this makes no difference
to our project. The name of this link, the About link and
finally the components. Good. This is all done. Make sure you have
a comma between each one of these routes. Give this a save. If we now go over to the browser, refresh. On forward slash menu, we have the menu texts
from our components. We also have Admin. You see the admin text, About, then the forward slash
for the home directory. Currently, we still see the
content of our four views alongside the existing
content on the left. How does our app know where we want these
views to display? Well, the answer to this lies
inside of the RouterView, which we're going to look
at next along with adding these new views to
our navigation links.
7. RouterLink & RouterView: Inside of our source
folder in the project, open up the App.vue. Inside of this file, there are two main
things here to note. One of them is we have
this navigation area, and this contains
two router links. This has the text of
home and also about, and this is the
two links which we see inside of our project. The first thing here is these links are not the
usual eight elements, which we'll see in regular HTML. Instead, they are surrounded
inside of this router link. This is how the
view router handles linking to other
pages or components. It uses these instead of the
a elements so the router can change the page URLs
without reloading the page. We can also control the
URL which we are linking to include dynamic routes, which may include variables, rather than just
having the plain texts like we have here, we can also insert
variables too. Also, you'll see
this router view component down at the bottom. This is the outlet which
displays our page contents, which we have inside
of the views. This is where our page
content is displayed, giving us full control
rather than just replacing the full page content. Here we see this
router view component is placed alongside the header. This is why we see them
side-by-side inside the browser. Also, you notice if you go
up to the top of the file, both the router link and the router view is imported
from the view router package. With this knowledge,
we can remove the boilerplate code from here and create our own links inside
of the header components. Let's clean up this App.vue. We'll take out our router link, we no longer need this. Also remove this
header content too. We don't need this
HelloWorld example inside of our project, but we can reuse this file and convert it to be the
header component. We'll call this the app header, will rename the
file AppHeader.vue. Then inside the
template we can place this AppHeader exactly
where we want this to be. Since this App.vue file is the main component
for our project, we can use this to give
this some structure. Inside of our projects
we always want this router view to
render the page. But just above this, we can
also place in the AppHeader. This means on every
single page in our project will have
the header at the top, followed by the page content. Next, remember we've changed this filename to
be AppHeader.vue. This is inside of the
components folder. Jump inside of here. Then rename the HelloWorld example
to be AppHeader. We can also remove these icons. [inaudible] created the project. We don't need this
for our project. Let's jump into this
AppHeader.vue file. We clear out all
of the contents. We currently don't need a
script. We'll remove this. Remove all of the
default styling. We've all the contents
between the templates. I'm going to replace
this with a HTML header. Row header place this inside
of a Level 1 elements with the class qoute sight
underscore title. Rather than just simply adding the text of pizza planet
for our site title, we can also place this
inside of the router link. This has an opening
and closing tag. Then inside of the Tilder icons, the text of pizza planet. You can see we've
got this underlying which data is an error
with this component. This is because we need to add the two prop as an attribute. Since this is the site title, this is just going to link
back to the homepage, which is forward slash. The next a navigation section
with our navigation links. These navigation links are
going to link to our views. We're going places
just below our Level 1 heading. The nav elements. This is a series
of relative links. The class of link which we'll
link to our CSS very soon. The two attributes. This will be the home link,
so we can link to forward slash and the display
text of home. Duplicate this three more times. The next one is for the menu, which links to /menu. The last one is to link to the admin below the navigation and placing the
Level 2 heading with the text of the
Number 1 place for pizza Let's give this
a save and check. Everything is working. We've now got the app header at the top and then we should see the router view
content just below. Refresh. There's our
Level one heading. We have our links, we have the Level 2 heading, but we still have this
page content alongside. The content we want
it to display in, but we don't have the
required at styling. This happens because of
the styling which is setup in our VP
project by default. We could remove this
and use our own.. Into the sidebar. Jump into the assets folder, various style sheets
inside of here. Let's remove the base.css. We can also remove the logo, but we can't make use
of this main CSS file, but instead we'll clear out
the contents and add our own. Select all of the contents
and delete this file. It's saved over to the browser. This is our header
section. Now at the top. Now I have the page content
just below or we can also switch between
this page content with these header links.
8. Named Routes: As we've just discovered
inside of the router link, we linked to various pages
with these two attributes. Then inside it, placed in a
hard-coded URL to link to. This works completely fine, but we also have available
something called name routes. Name route may be a
benefit if we use the same link multiple times. Imagine inside of
our project we had many links to this menu page, but instead in the future if we maybe wanted to change this URL from forward slash menu to be something forward slash pizzas, we need to change all of
these links manually. Alternatively, we can
link to the name, which we gave it inside
of the router file, so jumping to the
router in the index.js. Remember each one of these
links has a specific name. We can pass this
in as an object, instead of the forward slash, placing the curly braces
to create an object. The name property,
which is equal to home. Just like any other
view Js attributes, if we're placing in dynamic
data inside of here, such as our name, we also
need to place in the colon, which is v bind to
make this dynamic. This tells a view router, that this is not to be
read as a string of text, but instead take a look for this name which we have
in our router file. We can also do this with
the rest of the links. Copy this and we can
place this at the top of our title. The second one. This was for our menuLink, which matches the name
inside of our router file. The number three. This
was the aboutLink. Then lastly the adminLink. Save this and head over to our project and it
should be automatically updated since we're using vite for our development server. Click on all links and everything now still
works the same as before, but now what we're using
is these name routes.
9. Nested Routes: Our current setup allows for a router structure
just like this. We have our RouterLinks
in the header, and then replaced the
contents of these views where we wanted it using
the RouterView. This leads to a pretty
simple URL structure. We would have our main URL and each view would add to the
end the name of admin, menu, or about, for example. But sometimes, our structure
may need to go even deeper. One of these views may also need links and a place to
display the content. An example of this is in
our projects About Us page. On the left is the
About Us view, and we can place in any
content which we want to. Additionally, we
can also provide extra links as we see
here with the history, delivery, and locations. On the right, we have the desired structure which would be the About Us view then the
nested links of history, delivery, and
locations following. To structure this, we
again need to make use of the view router using
the RouterLinks and the RouterView to
place the contents onto the About Us page exactly
where we want it to be. Let's head into the project and we can begin to set things up. These history, locations, and delivery sections
which we've just seen will all be components which
we can switch between. Let's set these up in
the components folder. We already have this setup,
so create a new file, and this one is going
to be Delivery.vue. The next one, this is
going to be History.vue. Then the next one is locations, all with the.vue extension, and we can also remove the components which
you don't need, which is TheWelcome.vue, move this one, and
also WelcomeItem. Good. Now let's go into
our three new components, and we can add some
basic structure. We need the template
and each one of these three components is
going to be pretty simple. We'll just create a wrapper. We'll add a title at the top, an image, and also a
little bit of random text. The div, look at the C
class of info_block. This will be added
to all three of these new components, h3. I'm going to make use
of the tilde icons and this is Delivery Info. After this an image source. Let's go into the assets folder where our images folder will lie and then at the image
which you want is the box.jpg. Again, you can use your
own images if you prefer. Just replace any one of
these which you want to. The alt text, pizza in box. Then also a class of info_img. Just after the image, we'll add some text
in the p elements, and you can replace this with
any text which you want to. But for simplicity,
I'm just going to add some random
lorem ipsum text. If you're using Visual Studio, you can simply type the word
lorem and then hit "Enter". This will give us
some dummy text which we can place
in our project. To keep things consistent, let's copy all of these contents and then go into the history. Paste this in and all we
need to do here is to change the title from delivery
to be history. The image, this one is
the chef image.jpg. The alt text of
chef making pizza. Give this a save. The
next one was locations. Again paste in the contents
and change the title. The image for this
one is tables. Also change the alt text. This is the outdoor tables. Good. These are all
three components which we'll use
to switch between in the about view and this is what we are
going to be looking at next.
10. The About View & Child Components: Now let's head into the About
view page where we can now use a router to switch between
our three new components. We also need some
structure and content in this page along with the
router links and router view. Let's remove the
Level 3 heading and place in a div with
the class of about. This will be the wrapper for this components and then we'll
place in various sections. The top section, if
we go to the about, this is going to be
placed in two areas. If we go over to the about view, this is going to be
placed in two sections. The top section will
be an introduction about this page where we'll have some texts
about the pizza, the history, we'll
place in an image, and then below this we'll add a second section
which will contain our router links to switch between our freedom components. The first section with
the class of intro, and nested inside
we'll have two things. First of all is a div, and second of all is the image. The div is going to contain the text and then we'll
add the image just below, and then we'll add a new
section down at the bottom, and this one will
contain our links. Let's start with this first
div by giving this a class of info text wrapper underscore
is between each word. Level 3 heading with the title of quality pizza for 30 years. Let's check this out.
There's our title, and then below our
Level 3 heading, place in a p elements with
the text of nothing but the best quality ingredients
with a friendly atmosphere. There we go, placed
in a class so we can add some CSS
later of info text. Next, the image. First of all, a class
again for the styling of info_image or img. The source jump into the assets folder where our
images folder is contained, and this one is going
to the calzone.jpg. The old text calzone
close-up image. There we go. This is the top
section now complete. Of course we'll make it
look a little bit better soon with some
styling, but for now, let's jump down to
our second section, which is going to
be for our links. This needs a class of
more info wrapper, a Level 3 title, and
this is going to say click on the links
below for more info. Then we can start to
construct our links places in the HTML nav elements,
unordered list. Each one of these links
is going to be placed inside of the router link. Each one will have the class
of link, but our styling. We use the colon because
we're going to make the two attribute dynamic by
placing in an object, and this object is going
to point to the name, which we're going
to assume give to each one of these components. This first one will
be the history link and the display text
of our history. Let's duplicate this
two more times. The second one is going to
be for the delivery link. A text of delivery. Number 3 is going to
be the locations link. Any text of locations. Good. This is
everything we need, now we've got our
free links sitting below all of our information at the top. Let's check this out. Into the About page. The contents disappear,
this may be just because we don't have a
match for our names, because we've not
created it yet. Let's just jump into the
console and we can check this. We've got no match for
our name so let's go over to the router
and do this now. The router index.js. We first need to import our
three components which is history, locations,
and delivery. The top, import delivery this time in the components folder the
component is delivery.vue. Duplicate this two more times. The next one is for history. The last one is for locations. As with all of our other routes, we need to add these three
components into a path. But remember, the path for
these ones is going to be nested inside of the about
link so what we want. In fact, we can just
remove the second about. We've got the component name, that's about so we can just remove the one which was
placed in by default. We have this About
link and what we want is the path to be about, and then forward slash,
we want the locations, delivery, and also the history. How do we go about this? Well, currently the forward
slash about link is only pointing to one
single components. We can also nest inside multiple components by adding
the children property. For the children
property since we're linking to multiple components, this needs to be placed
inside of an array. Each array contains an
object which is structured exactly like each one of these routes so we'll
insert the path, the name, and also
the components. For the first one to link to history we place in the path of history, the component name. This component name needs to
match the exact one which we placed inside of
our router links. I've got history
link, delivery link , and locations link. This one is for
the history link. Then the history components. Separate these with a
comma. Close this down. Separate these with a
comma and place these onto their own separate line. Let's duplicate this, copy and paste this
in two more times. The second one is for delivery, link to delivery link on
the delivery component. Next, locations. Let's give this a try.
We need to jump into the About Us page and
then we can check out exactly how this will look. Currently we're on
forward slash about, let's try our links at the
bottom, we've got history. We're now about forward
slash history, delivery, this is added to the end and
also the same for locations. One thing you may be thinking is we currently have forward slash about which we've placed
inside of this path here, then we also have
forward slash locations. First when we've setup
to the child routes, we've not added a forward slash before each one of these paths. Well, if we did this, it will
be treated as a root path. Let's take a look at exactly
what we mean by this. If we add the forward
slash to each one of these and then go
into the About. Now, instead of having
forward slash about, if we click on one of our links, the full URL will
be replaced with this history delivery
or locations link. But since we want
to add these onto the end of the About link, we can just use these
without the forward slash. Finally, to actually
see the contents below from each one
of these components, we need to go ahead
and add a router view. Go to the About view
and then just below the navigation and
placing the router view. This and let's check this out. We've got the top section and then the locations
area at the bottom, the history, and also
the delivery too.
11. Named Views: This About Us page has three components which
we can switch between. As we know, we can
click on the history, the locations, and also
the delivery components. As we've also
previously looked at, we can make use
of components and reuse them in any file
which you want to. For example, in the app.vue, we can go the traditional
way by importing a components and then rendering this inside
of the template. An alternative way of rendering a component is to make
use of the Vue Router. So far we've used
the RouterViews to display the page such
as this one just here. Also the same in the above
view which we just added in the previous video we made
use of the RouterView. This could display either
the top level page or any child components, just like we do here
with the about page. We can also insert as many
more RouterViews as we want to and each additional
RouterViews will act as outlets with various components. If you were to insert
more RouterViews, we also need to give
them names so the router knows which components
to display in each one. Let's say we wanted to show some additional information at the bottom of the homepage, such as the delivery
and history components. Here we have a developer. The key here is we have two
additional RouterViews. Each of these has a name attributes which will be
important in just a moment. Notice the RouterView
at the top has no name. This is a default location. This default, along
with the two names are set inside of the
routers index page. The left shows what we
currently have set up for the homepage using
a single component. However, when we have multiple
RouterViews with names, we need to modify this to
use a components object, just like we see
over on the right. First is the default location, which is the RouterView
with no name. Then the next two
names will match the two names which we
gave to the RouterViews. Go to the project and
we can set this up. Jump into the main
top-level component, which is the app.vue and then just below the RouterView
we'll place in a div. This div, the styling and layout will have the class of
info block wrapper. Then inside of here we can place an additional RouterViews to display any one of
our components. For this example, I'm going
to display the delivery and also the history components
inside of this main page. Placing a RouterView, the name attributes,
which is going to be equal to delivery. Copy and paste this.
The second one is going to be for history. We only want these
two components to display on the homepage. For this, let's go over to the routers index page and
go up to the home section. Currently the home
router only shows the single component
of home view. But to display
multiple components, we can change component
to have an s on the end. Remove this, pass in an object. Just as we've seen
with the slides, we need to add the default view, which is the one
which is displayed inside the router
view with no name. This one is the home view, the name of delivery. This will show the
delivery components and the next one is for history. Just to recap, we've got
the default component, which is the home view
inside of the app. This will display
in the router view, which has no name. Next, we have the delivery
and the history components, which will match up
with the name of our two additional router views. Now, view router knows which components to
display in each location. We already have
these two components of delivery in history imported at the top so by
now it should be good to go. Save this file, over
to the homepage. From the very top we've got
this Level 3 heading of home, which we can see
inside of our view. What we have here is this
title and then below we've got our delivery
components and the history. Just to confirm these only
display on the homepage, we can go into the
additional links and we shouldn't see these
components displayed. To finish things off,
let's go back over to our app.vue and add a
little styling to make these appear side-by-side
on the larger view and then stacked vertically
on the smallest screen. App.vue. We have this class
of info block wrapper. Let's copy this down
to the style section. The display type of flex. We'll begin with the
small screen view, which means we need to change the flex direction to be column. This will keep our
content stacked vertically on the
smaller screen. To make this change to
be alongside each other, we need to change the
flex direction to be row on the larger screen. We can do this by
adding a media query. In fact, we can also
remove all of the rest of the styling. I don't
need any of this. Set up all media
query with our media. We want this to
target screens with the minimum width of 900 pixels. Let's grab our info
block wrapper. We will modify the flex
direction to be equal to row. On the larger screens of 900, these in a row and then this drops down on
the smallest screen. This is an alternative way of using components rather than the traditional way of importing a component into another
file to display. We can also allow the Vue Router to place these into
a router view.
12. Project UI- Section Intro: This new section is
all about getting content into our application. We'll create all of the
structure we need by creating various
pages and components. Later on, in the
upcoming sections, we'll make these more
dynamic by inserting various pieces of
information from a database, such as our orders, our users, and our pizzas. But next, we'll start by
creating the menu components.
13. The Menu UI: The menu view page, which we're now going
to create will be split into two sections. On the left, we're going
to include the menu, which will include all
of the pizzas in a list. Then in the upcoming section, we'll create the basket
section over on the right, which will list all the contents which the user adds
to the basket. Let's jump into our
menu view components and begin to work on this Into the views
in menu view. We're going to move
the Level 3 heading for now and then create a div, which is going to
be a wrapper for both of our two sections. This the class of menu
underscore wrapper. Then two more divs, which is
going to be nested inside. The first one is going to be for the contents on the
left-hand side, which will be our menu. Will give this the
class of menu, and then create a second div, which is going to
be for our basket. Give us the class of basket. Then jump up to the
menu section and place in a Level 3 heading. Make use of the utilas any texts of authentic
handmade pizza. For our second div,
which is the basket. Again, a Level 3
heading up the utilas. This one is for the basket. As mentioned before, for now, we're just going to work
in this menu section, which is going to
be a HTML table. This table will just contain
a single pizza for now. But we will come back to
this when we start to work with the firebase
database so we can loop over all of
the pizzas which are stored and then display
them into this table. For now, we'll set
up our template of how this is going to look inside of the table body. Then at the TR element
for our first table row. All of our pizza content will go inside of this one
single table row. The cells enter some table data. This is going to be
for the pizza name. The pizza name will place
inside of the strong tags and place in any pizza
name, such as Margarita. Below this first table row, we'll add in a second row. This one will contain
the description inside the table data. The small tags and the description of a
delicious tomato paste pizza topped with mozzarella. Scroll down and just blow this table row will
add a third row. This one is going to be
for the available options. For each pizza, we're
going to create two different sizes, which will be a
nine-inch and a 12-inch. Later on during this class, we will loop over
this and do this for both the 12 and the
nine-inch pizza but for now, since we're just creating
our structure and adding some sample data, I will just place
in one single-size, which will be for the nine-inch. This will be followed
by the price. Then the third section will be a button which will be used to add the pizza to the basket. Place in a HTML button
inside of here. The type of button. Then to get out plus symbol will make use of a HTML entity, which is the code
of the ampersands, the hash 43, and a semicolon. The browser let's
see how this looks. We got our first row, which
is the title, the second row, which is a description and the third section is
going to be our option. As mentioned before, we will loop over this third section, which will give us
our various options. This is our table
for our pizza now setup and then upcoming video we'll move on
to the next section, which is the basket.
14. The Basket UI: Following on from
the previous video, where we added the menu
section on the left, we're going to now move
down to the baskets area. This basket will also be a
table showing the pizzas which the user selects by clicking
on the Add button just here. Followed by an order, total section and a button to then go ahead and
place the order. In the future, this order
button will be linked up to our JavaScript to then push
this order to our database. For the table, sections
will contain a single row. This row will have the
quantity and a button either side to either increase
or decrease the quantity. You will have the name
of the selected pizza and also the price too. Below our Level 3 heading for
the baskets, create a div. This div will contain the
structure for all baskets, which will be a table
at the very top, then the text, which is
going to be the order total. But now just place in any
value which you want to. This will be updated and
calculated with JavaScript. Next, a button which
will be responsible later on for placing the order. Some text is fine for now. Let's check how this is looking. There's one of our
content. Now we can move into the table section. The table will contain
a single table row. In the first table
data cell is going to be two buttons and
also the quantity. We'll have the quantity
in the middle, and then a decrease
button on the left and an increase
button to the right. The first button with the
class of quantity_button, the type, which is also button, and then the HTML entity, which is the ampersands, the hash 8722 followed
by the semicolon. This will give us the negative
symbol and then we'll add the quantity and then to the right a button
with the plus symbol. For the quantity which
will sit in the middle, place in any quantity for now. Copy our button and
paste this in at the bottom and simply change
the code number to be 43. Dates on these
buttons will be used to increase and
decrease this value. Before we add this
to the basket, we will also update
the order total as we change things too. That's our first
piece of table data. The second one is
going to be for the pizza name and also the option size
and the third one. This is the price of the pizza. Save and over to the browser. This is all the information
we now need for the basket and with all of
this content now in place, we'll next look at adding a
little styling to make it look a little bit better and
also improve the layout.
15. Fonts & Base Styles: Inside of our project, we
have this main.js file. This is the file
which is responsible for creating our app. We import this from
the vue library. We also import the
main app components, which is this one just here. This is the main entry
component and everything else is going to
be nested inside. We pass this file to create app, and then we mount
the application to the DOM with the id of app. As well as this, it also
includes an import at the top with a main.css file, which is inside of
this assets folder. The way we structure
our styling is completely up to us when
it comes to a vue app. But, I'm going to be
using this as a kind of general style file containing base styles such as
fonts and resets. We can still add
specific components or page styling in individual
components of vue files. First though, we need
to choose our fonts, which we're going to
use for the project. For this, I'm going to
make use of Google fonts. So head over to
fonts.google.com. I'm going to make use
of two separate fonts. One of them is going to be
for our headings and title, and the other one for
the general text. So click on Search. The first one is
called Marck script and this is M-A-R-C-K, and this is the one for
our headings and title. So select this. Scroll down. This one only has the
regular texts which is fine. Click on the plus
icon to add this to our selected families,
as well as this. Let's search for our second one. Back to the search.
For the general text, I'm going to be using Roboto. This is the one we need here. Let's go for a
selection of fonts. So 300, which is a
lightweight text, the 400, which is regular weight, and also a 500. Add all three of
these to the family. Click on these selected families and we have the
information which we need to import
into our project. So we have a link
which we can use or we can use the import syntax. To import this, let's go over to the Assets folder and
into the main.css. This is currently empty. We can add this import rule. So copy everything inside of the style tags. Paste this in. As we've seen before in
the main dot js file, we have the id of app, which we can select as the
main wrapper for our project. So select this with the hash. Inside of here, we can
set the font family to be Roboto by copying this
second line just here. This would be our
general text and we'll override this with the Marck script when we
need to. Enter the body. We're just adding general styles inside of here, remember? So we'll add some margin
to center our project, by setting this to be zero
on the top and bottom, and also on the left and right. The color row text of RGB, placing a red, green, and blue value of 76. Just after the body, we'll
target the headings h1, h2, h3, h4, h5, and h6. Remember, the font-family which we are going
to be using for this one is the other one which
we get from Google fonts. So copy this section, paste in. Remove any default
margin which is applied to any of our
headings with the browser. I'm placing the color, which
is an RGB value of 76,76,76. So this gives us our
general heading stylings, but we'll make use of the level three heading
in multiple places. So we'll be using it with
page and component headings. So we'll set the font size to be a larger size of 2.2 rems. Also, place some margin
on the top and bottom of one rem and zero on
the left and right. Next, the unordered
list, the ul elements. Reset any space in which
is applied by the browser, so default margin can
be removed to zero, and also the padding to be
reset back to zero too. The list items, and in fact, also the links too with the a elements will
inherit the color. This will remove that
purple link color, which we have by default
inside the browser. Also, reset the
text-decoration to be none, and also the list style. This will ensure none
of our links or any of our list items
have any underlines, and also any of the bullets
to the side of the link too. So some more general styles, we'll jump into our
image elements, and make sure that
the maximum width of any image does not exceed 100%. So next onto our classes
for our router links, if you remember early on, we have these links up at the very top and you can already start to see our
styling taking place. We have these menu
links up at the top, which you can see if we
go into the components and then into the app header. Each one of these router
links has this class of link. Let's grab this, a dot
since it's a class, a margin of one rem to
give this some spacing, there we go, and
we'll also make sure that each one of these changes color when we hover over them. So to do this, we'll target
our link once again. We'll use the hover selector, change the text color to
be an RGB value of 161. So the red, green of 132, and 132 also for the blue. Finally, for any buttons
which have on our site, we'll also set the default
styling for each one of these. So the button, will
remove the background, the default color, by
setting this to be none. A very fine border
of 0.5 pixels, a solid line, and the color
of this line is going to be an RGB value of 202
for each one of these. Some border-radius of 0.3 rems, some padding to make the
button a little bit bigger. So half a rem on the top and bottom and one rem on
the left and right. I need to prompt the user
that they can click on these buttons will change
the cursor to be a pointer. So, if we go over
to our project, we can see some of these
have taken effect. But remember, these
are just general styles for our project, and we'll add more
specific styles to each one of our individual
pages and components.
16. Menu & Basket Styling: Next we're going to head into
the Menu View File and add any specific styling
to this view components. Open this up. Then just below the templates, we'll add the style and add
this scoped attributes. If you've not used the
scoped attribute before, this means that all
of the styles will only apply to this components, i.e., the templates
which we created above. To see these in
action, we'll jump in to the menu section. Make this a little bit smaller. At the top we've got
a level 3 heading, which is this one just here. Let's grab this and set the text-align
to be in the center. This also applies to our basket. Both this top section and
this bottom section will soon be applied to the left
and right on the larger view. But first we'll start
with the mobile layout, which is the small screen. Let's just below our h3
and say the mobile layout. To switch between
our two layouts, we're going to make
use of the flexbox. If we go to the very
top, you can see that this div has the class
of menu wrapper. This will be the one which is
responsible for controlling the direction of our menu
and also our basket. These two divs are the child
elements of menu wrapper. If we go down and
we select this, we can make use of the flexbox. Menu wrapper. The display
will be the flex. You can see the default
flex direction when using the flexboxes in a row. These will be placed alongside
each other on the page. This is the layout which
you want on the wider view. But for the smaller view, we need to override this to
be the flex direction of column. Also, the color. The font color will be
an RGB value of 80, 96, and 112. Next we have the two sections which were inside which
we just looked at, which is the menu section up at the top and then
the basket section. Each one of these has their
own corresponding class. We have.menu and also.basket. We set the background
for each one of these sections to
be a hex color. The one I'm going to
go for is faf1e2. The border radius for each
one of these sections, small value of three pixels. The height, just in case we
don't have a lot of content, we'll make sure that each
one of these sections is a minimum of 100 percent
of the viewport height. Then some padding on the
inside of these elements to give you some spacing from
the edge of the screen. This is the direction which
we have for the mobile view. If we stretch this to
be a little bit wider, we want to flip
this around to make the flex direction to be a row. We can do this inside
of a media query, where I want the screen to be a minimum width of 900 pixels. Please take effect. Now, what we'll do is we'll grab the menu wrapper section, copy this, paste this inside. We've already set the
display type to be flex. We're going to remove this. I've already set the color.
We're going to remove this. All we need to do here is to set the flex direction
to be in a row. Add some space between
with justify content. Then we need to
decide how much space each one of these sections
is going to take up. The pizza section will probably need a little
bit more space. We can make use of
the flex direction to give us more space
than the basket. For the larger view, let's
grab the menu section, set the flex value to be two. Then for our basket, we'll set the flex
value to be one. This means that this
will try to take up twice the available space
as the basket section. You can see this is reflected
inside the browser. We can tell the difference
between these two sections. We'll add a border
to the right of this menu to add a vertical
line down the center. Inside the menu, set the border on the
right of one pixel, a solid line and the RGB
color of 202 for each value. There we go. We're almost
done now for this section, but what I'm going to do
for these two buttons here is to remove the border, make them a little bit smaller. If we go up to the section, we see each one of these buttons has the class of
quantity_button. We can do this in
the general section, just above the media query. Quantity_ btn. Remove the border. Let's set the padding to be a
value of 0.4 rems. This now reduces
the padding now for both of these quantity buttons. Next, we'll continue with
the theme of styling. We'll add some styling
to our header section.
17. Header Styling: Surely we're going to move on to adding some new components and also some new
items to our site. But quickly, just before
we do this we'll apply some styling in it to
this header section. Let's open up the sidebar, jump into our Components, and then the AppHeader. The AppHeader needs the
style section at the bottom, and the style section
is going to be scoped to these
particular components. Let's take a look
at what we've got. We've got the site
title class at the very top. Let's start with this. Site_title. Begin
with the font size for our site title and
we'll go for 3.6 rems. Make it pretty big. The
color of Alice blue. We can't see it too
much at the moment but very shortly
we're going to add a background color to
this header section to make it stand out even more. We'll add a transform
to make this rotate at a certain value by adding
the rotate function. Pass in the amount of degrees which you want to
rotate this by, and this can be a positive or a negative number.
Eight degrees. You can see this now
has a tilt and you can increase or decrease this value. To give it some space
from the content below, add some margin onto the
bottom of the elements. Let's go for two rems. Then we can grab
our header which is the wrapper for
this full section. The header is going
to make use of the CSS Flexbox to
lay things out. For both the smaller and
the larger screen views, these contents will be placed vertically in the
flex-direction of column. Set the display type. Remember the default
flex direction is row which is left to
right across the page. We can switch this around
to use the column. To place the content
into the center, use the flex property
of align-items. We can see that the content
is pretty much against the top and also the
bottom of the header. We'll add some space in
with a padding value of one rem on the top and bottom and zero on the left and right. For the menu links, we'll
match the color of Alice blue and then place in
the background image. For the background
image will point to a URL which is stored
inside of our project. Let's go into the assets folder, into the Images, and then inside of here we
have an image called main-bg. This is dot dot slash, jump into the assets folder,
images, main background. We also want to set
this to be no-repeat. Since we've added a background, we also want to
add to the height property and set this to be a fixed value of 650 pixels. The background size, we'll set this to cover. Then if we take a
look at this image inside of the images folder, the main-bg, we can see
this image is not centered, it's cut off on the
right-hand side. Let's fix this and go
back into our CSS. Make use of the property
called Background Position. Set this be in the center. This image will now always be centered on different
screen sizes. Next little tech
shadow to each one of these characters of one pixel, one pixel, three pixels, and the color to apply for this tech shadow
is going to be an RGB value of 20, 20, 20. The next thing to do is this
level to heading which is the text of the Number
1 place for pizza. We can grab this with the h2 selector and add some spacing. A margin property of two rems on the top and bottom and
zero on the left and right. A text color of antique white and the font
size of two rems. Good. This is our styling
now complete for the header. Our site looks a
little bit nicer. Next, we're going to move on to the Admin page and
create a new pizza form.
18. Admin: Add New Pizza Form: Inside of our admin view, very shortly this is
going to be split up into multiple areas. It makes sense to
organize all of these areas into
different components, and also place these components
into an admin folder. Let's jump into the sidebar. Into the components,
and inside of here. Create a new folder with this new folder
icon called admin, and we'll group together all of the admin related components. Inside of here,
create a new file. This one's going to be the
first component for our admin, which will be a form
to create a new pizza. We'll call this
the NewPizza.vue. Create the template
which we need for this section and the
section wrapper. This will have the
class of admin_section. This will be split up to
have a header section with a title and also the form
to add our new pizza. The header for this section, the class of
admin_section_header, h3. Text of add new pizza. Before we go any further
with any more content, let's check how this
looks inside the browser, but first we need to import
this into our admin. Jump down to the views
and the admin view. At the very top, place in our
script to import our file. What we're going
to do here is add an attribute called setup. We'll come back to this setup
very soon in this course, but for now we're going to import the component
which we created, which is going to be
called new pizza. I'm going to import
this from the directory which is @ to take
us to the route, the components folder, admin, and the filename of new pizza. We got level three
heading for now we can import our components, and place this below. Instantly, as soon as we save, we'll see this is added
to the admin section, so go into the admin link
and this is the title which we added in the
new pizza components. Continuing on and below the
title, create our form. We'll remove the action
since fueled by handling all of the necessary
JavaScript's. Each one of our sections, such as the pizza
name, the description, and the various options were surrounded inside
of a div styling. This div will have the
class of form_group. Then this will have a
label and an input. The label, the text
of name, the input. The first one for the pizza
name will have the type of text and the ID of name
which will match our label. Let's copy this div section. We'll duplicate this
and paste it in below. The second one is going to
be for the description. For this one, we
don't need an input, we're going to use a text area, so we could have
multiple lines of text or a long description. We're going to move all of
its content inside of here. Let's clean this up, and we'll add the rows which we need. Let's set the default
value to five and also an ID of description, which will match the
label just above. Save this, and we
can see our inputs are now appearing on the screen. Next, we're going to
create two new sections, which is going to
be for our first and our second pizza options. This is going to give us two different options
for the pizza size. The text inside the P
elements will create these as strong elements to make
them more bold. Option 1. Then we can continue
with our form elements. We'll copy the font
group from before, the very top one, write this in, and this one is going
to be for the size. This is going to be in inches. The label of size one. The type of text is
completely fine, and we'll also match
his ID with the label. Below this, we also need to
create a new form group, and this one is still
related to Option 1, and this is the price of
this particular size. The text of price, the label for Price
1, the matching ID. Then we'll carefully duplicate
this section so the price, the size, and also
the text. Copy this. Paste in it below to
create a second section. This one is for Option 2. Let's change the
property names of each one of these
attributes to be Size 2. The same for the price. Save this, and over
to the browser. We now have a two sections
to fill in the sizes. The final section
is going to be for a button to submit this form. Again, place this
inside of a div with the class of form group. This will keep the
styling consistent. Add a button with
the text of add. You will now add new
pizza form now complete. We'll come back to this later
on and improve the styling. But next we'll move on to
another admin components, which is to display the pizzas.
19. Admin: Pizzas Component: So we've got the add
new pizza components inside of the admin. The next admin
component which we'll create is a pizzas component, which will display all of the available
pizzas on the menu. I'll jump into the
sidebar and create a new Vue.js file again inside the admin to keep
this organized. This will be simply Pizzas.vue. As ever, close the
sidebar down and create a template to
store our HTML code. A section, which
is going to have the class of admin_section. Also take a look at the new pizza component
which we created previously. This also matches
this same section. So we can apply the
same CSS styling. This will also have a header. Again, match in this header just here with the
same class name. So the class was
admin_section_header. This will give us
some consistent style for all of our admin blocks, Level 3 heading with
the text of menu. So this is all we need
for the header section. Let's jump below the header
and create our table, which will list all of the available pizzas
from the menu. The thead section, or
the table heading, place in a row, and the th element to create
our two headings. So we're going to keep
this pretty simple. All we do is add a
pizza name on the left, and then on the
right of the table, we'll add a button to remove
this pizza from the menu. So the heading of pizza and the second one is going to be the text of remove from menu. I will set up a function
later to do this. Below the thead, create
the tbody section. The first row, which will
sit below our headings. So the first thing
inside of here is the pizza name inside
the table data elements, and we'll just add some sample
text inside of here for our pizza name and
we'll link this up to our database later on. Next, the next cell inside
of the table data is a button which is going to remove the pizza from the menu. The button has the type
of button and also a class for our
styling of btn_remove. Then inside a HTML entity, which is going to be the cross. This is the ampersand, the word of times, followed by a semicolon. This is all of the
content which we need for now and later on we'll create a for loop to loop
through all of the available pizzas and duplicate each one
of these rows. Also as with the new
pizza components, we need to import this into
the admin view, into here. Duplicate this and this one is going to be for all pizzas. This is in the
same admin folder. So we change the name, and I'll put this at the bottom of the file into the browser. There's our table
with our single row and our single pizza. Next, we'll create the
final admin components to list all of the
customer orders.
20. Admin: Listing Orders Component: The final admin
components which we need will be used to
list the current orders. Again, I just have these
components inside of the admin folder to
keep this organized. Create a new file and this one is going to
be the orders.vue. This will also follow
a similar pattern to the previous ones where
we create a template, we create a section with
the class of admin section, we create a header, and then a table to
list all of the orders. Let's get started with the template and the
section at the very top. This is our wrapper. The section also needs to have a class of admin section header. Again, this matches
the previous admin components which we created, as does the header at the top, the class, and if I could just use the
wrong one at the top. Let's copy this. This one should be admin
section header and the section should be the
class of admin section. Good. We're now back
on track and we can create inside of the header, our Level 3 heading with
the text of current orders. Also just like the
pizza's components which we just created, later on when we link
this to Firebase, all of our information will
be pulled from our database. Meaning it will be dynamic. For now, we'll just place in some dummy data and we'll
add five current orders and this will go up
and down depending on how many orders we actually
have in our database. Next, the table section, the table row, the th
tags for the heading. The orders are going
to list out the item, which will be the pizza name. Remember that each pizza
has two different sizes, so we also need to list this. We need to know the quantity
and also the price. We'll add inside of the
brackets the word of total because this will
be the total value, which will be the
number of pizzas multiplied by the price. Then below our first table row, add in the second row, and for now, we'll place
in a sample order. The first cell inside of the table data element is going to be for
the order number. This cell will also contain
two pieces of information. First is the actual
order number, and this will go up depending on how many orders we have, and we'll also add a button to remove this order
from the database, which will be useful once
the order is completed. Inside of the strong tags, we'll add the order number. Let's place in number 1 for now. The button, the HTML entity, which will be the ampersand and times, followed
by a semicolon, and this will be
a little cross to remove this from our database, the class for our
styling of btn_remove, and also the type of button. This order number and this
Remove button will be on its own independent row
and then the third row, which we'll add just below, is going to be all the
values below our headings. Therefore, the first piece of table data we need is the item. This is going to
be the pizza name, followed by the
size, the quantity. Any values are fine for
now inside of here. The last one we
need is the price. To see this, jump
into our admin view. Now we can import this
at the top of our file. This one is the orders. I'll place this at the bottom of the template. Scroll down. Good. We've got a lot
of the user interface, so the admin section
now in place, and everything now
setup for adding some dynamic data
from our database. But next what we're
going to do is to jump into another
one of our views, which is the home view, and begin to add our content.
21. Home View: Next we'll get to work
on this home view, which is going to
be new components, and it's going to be switched
with the view router. Remember, we have this
header section at the top. Then at the bottom
we've already got two separate outlets placed
in via the RouterView. We've got the delivery
and the history sections, and is always going to be contained on the bottom
of our template. Remember just above this
we'll have our RouterView, which is going to display any components inside
of our views folder. We have our home
view already setup. We've got a simple template
with the text of home. We can see this just here. Remember our two components at the bottom which are placed
in with the RouterView, are only going to display
on this homepage, because if we go into the
router and the index page, we've set these two up
as the delivery and the history components
and the default, which will be the home view. This will be this
section up at the top. Let's jump into the
home view right now. Then we can get
to work by adding some content for our styling. We can set up the class of hot, and then
just below this, create a new div section, and this is going to have
the class of hot_text. Move up, a Level 3 heading. This will have the text
of hot out of the oven. There we go. A section for this homepage is also
going to have some text and also feature a
vegetarian hot_pizza, and also a image too which we have inside of the
images folder, which is inside of the assets. We need this fire.jpg. First the text underneath
the Level 3 heading, add a p element for our text. This one will have the
class of hot_pizza_name. Pizza name is vegetarian hot. Small text. This one will have the class of
hot_pizza_description. Then the text of our
signature vegetarian, but with a kick. Then below this text we can
place in a router link, which is going to
link to our menu. The RouterLink component,
the text of let's order. Then as ever, with
our RouterLink, we need to pass in the two
attributes with the colon, since this is going to
contain a dynamic data, pass in our dynamic
objects where we'll link to the name of menuLink. This menuLink is the same one which we setup in
the router file, which we can see in the router, and then into the index.js. These are the unique names
which we added to each one of these paths. Almost there. Now we have this div
section and then just below this we'll
place in our image. I will point to the image
which we just looked at, which was in the assets folder. We'll use../ to jump up one level in our directory
into the assets, images, and the fire.jpg. Class styling can be hot_img, and then also the alt text too. There's our content
all in place. Let's just check
this link works. This jumps to our menu
section, which is good. We can jump down below our template and add
the style section. This wants to be sculpted
to this component. We'll start with the main
section with the class of hot, and this is the wrapper
for this full section. This will be responsible
for changing the layout on the small
and the large screen view. To do this, we'll make use
of the display type of flex. Since we'll start with
the mobile first view, we can shrink down the browser, and then changes to be a column direction on
the smaller screen. Flex-direction is
equal to column. This will place our
content vertically. Then inside of a media
query on the larger screen, we'll switch this around
to make use of row. We'll do this now with @media, where we'll target the
minimum screen width of 900 pixels. Again, we'll grab the class of hot and change the flex-direction
to be equal to row. The small screen,
make this larger, and now two elements now
appear side-by-side. The first element was this div, and the second one is our image. Let's place this into the
center with align items. In fact, this just wants to go into the mobile first section, so supplies for
the small screen. Outside the media query, we'll go for the hot_image, and also the hot_text. This is a class they'll
place it in the dots. We've got the class of
hot_text and also our image, which is our two main sections. This is going to target
our two content areas, and we'll make
them the same size by setting the flex to be a value of two rems. Then the text-align of center. Next we have the heading, and
this doesn't have a class, so we can target a
Level 3 heading, which is a child
of the hot_text. The wrapper of hot_text
will select the h3, which is nested inside, and set the color to be
a hex value of bb2118. Which gives us this
nice red color, which we can see here. Then the hot_pizza_name, which is the
vegetarian hot_pizza. Make this a little bit bigger
by setting the font size. Let's try 2.2 rems. A little bit smaller too. 1.8. Next for the following
piece of text, this has the class of
hot_pizza_description. Copy this. Place this in. The font
style to be italic, the font-weight to be
a little bit lighter, let's go for 300. Let's check this on the larger
screen also. This is fine. For now, we can
go ahead and make some adjustments to suit
your taste if you want to. But next I'm going to move on to continuing with the
styling for this project. In particular, we'll add
some styling to our tables, and also our form inputs.
22. Form & Table Styling: To round off this
section, now let's jump into the main.CSS file, where we can start to
apply some shared styles between our forms
and our tables, and also some general
CSS styling too. From here, particularly
inside of the admin, we have some areas which
includes some shared classes. For example, if we go
into the components and into the admin folder,
we've got this new pizza. This has the section
wrapper of admin section, then we've got this
consistent header section with the same class in all
three of these components. We have it here at
the top of the orders and also at the top
of the pizzas too. We also have a form here which
we'll add some styling to. We have various tables for the menu and
the current orders, and we also have a table
inside of the menu view. We have various styles which are shared between
multiple components. Rather than places into
individual components, what we're going to
do is go into the main.CSS file and add the
shared code inside of there. As well as the main wrappers and also the forms and tables, we also have some
little details too such as the remove button which
we have in the menu, and also in the current orders. We've give this a
class of btn_remove. We'll add all of these
now into our style sheet. Go into the source, into the assets, and we'll
have this main CSS file. We've already added some
content inside of here, so let's go to the very
bottom below the button, and we'll continue on from
this button and go just below and add the
class of btn_remove, which we just looked up before. Since we're going to remove
something with this, we'd probably want to
add a color of red. The shade I'm going to go for is an rgb value of 180
row, 67, and 67. You can see instantly as
soon as you save both of these two buttons down at the bottom and now change color. We can also remove this border by setting this to be none, and make it a little bit
more visible by increasing the font size to be 1.2 rems. This menu section and also the current orders is contained
inside of a HTML table. For the main table element,
let's add some styling. We can place in a border radius, just a subtle border
radius of 0.3 rems. We'll see this more
clearly when we add some different colors. We can also set the text
align to be on the left. This will push all of
the text to the left of each one of the
individual cells. A width of 100 percent display
the full available width. Then we'll move down to
the individual cells which are contained
inside of the th, for our table heading and
also td for our table data. The spacing we'll add some
padding or some spacing on the inside of the
element of 0.6 rems. Next we'll work on this form for the add
new pizza section. Grab the main form wrapper, some padding of zero on the top and bottom and 1
rem on the left and right. Also we'll match the
border radius of the table by adding
this also to the form. Again, we'll see this
more clearly later on. Inside the new pizza,
we'll see that each one of these form elements
such as the name and the description is wrapped in this div with the
class of form group. This is consistent for each
one of these sections. Grab this. So this
is form_group. Some padding space out each one of these groups
and we'll add one rem vertically
on the top and bottom and zero on
the left and right. Make use of the flex box
with the display of flex. We'll align the items
into the center. Align items center as
you can see will give this vertical alignment so the text stays in the middle of each one of these sections. If we don't have this, you can see the
text is aligned to the very top of this element. We'll add a border to the very bottom of 0.5
rems for the thickness, a solid line and the
line color will be an rgb value of 202 for
each one of the colors. Whoops, that's a little
bit too thick there. This just wants
to be 0.5 pixels. Then we'll remove the underline from the very bottom section. Again, grab our form_group, select the last of type which is the very
last occurrence, which is our button, and then we'll remove
the border bottom by setting this to none. Next we'll give these labels and also our inputs a consistent
size using the flex box. The label will give these a consistent flex value of 1
so they all stay the same. Then because we want the input
to be a little bit bigger, we can target all of
the inputs and also the text area and give these a flex value of 3.This
means all of our inputs on the text area will
try to take up three times the available
space as our labels. Let's now work with
the borders for each one of our inputs. Just below the flex value
of 3, set up the border. This will take on the same
value as our border bottom. We can copy this, paste this in. This just gives us a lighter, more subtle border for
each one of our inputs. Some border radius of 0.3 rems, some padding to make
these a little bit bigger of 0.6 rems. By default, with HTML form
inputs and text areas, it doesn't inherit the
font and the color by default so we
need to do is to set these manually by setting
the font family to inherit from the parent and also
the same for the color. As we've seen
before, each one of these admin components
such as the new pizza, has this class of admin section, and then we have this
admin section header. Let go into our styling
and we can target these. First, the main wrapper
which is the admin section. Margin of 1 rem and
this will space out all of our components. The background color, which
is a hex value of faf1e2, and also finally
for this section a border radius of 0.3 rems. Next, the header
section for each one of these components
and remember these have the class of
admin_section_header. Set the display type to be
flex. Give this a save. This one looks fine
and as does this one, we'll have a little issue
but Add new new pizza. This shouldn't be side-by-side. We have new pizza, we have the wrapper, we have the header, the form. And in fact we just need to
make sure that this header is closed off so cut out the closing a tag for the header and paste this
just after our h3. That now looks
better and this now keeps us more consistent with the rest of these sections.
Back to the styling. And you may be wondering why exactly we have set
the flex box when we only have one single
element inside of here. Well, this is because
later on we're also going to add a
little icon over on the right to fold each one of these sections out of
view if we don't need it. Therefore, since we'll
have multiple sections, we can add justify content to add space between
each one of these. Also, to keep these
vertically aligned, align items into the center. Then lastly, some padding of 1 rem which will play on
all sides of this title. We're getting somewhere
now with the styling. This should also apply
to our menu section. Then the final section we'll add some style into in
this video inside of the home section is each one of these Info blocks
down to the bottom. These have the class
of info_block, which you can see if we go
into the main components, which is the App.vue. This is rendering
our delivery and our history components
so jump into delivery. This has the class of info block as does the history component. Back to our styling info_block. The display type of flex, the flex direction of column, align items into the center, add some padding to
each one of one rem, and also a different
background color for each one of our blocks and the hex value of faf1e2. Let's save this and
see how this looks. Over to the browser, we can
see on the smaller view these are stacked vertically
on top of each other. We'll stretch these wider. The two components are
side-by-side and we have this flex direction of
column for each one. We have the content
aligned into the center, a little bit of padding
and the background color. There is still some other
styling to apply to individual components but we'll add this as we go
through the course. But now we have lots
of content in place. In the next section, we'll add some JavaScript
and take a look at using the VueJS composition API.
23. Options Or Composition?: The Options API is
something which you may be familiar with if you've built
with Vue.js in the past, in Vue Version 1
or Vue Version 2. The good news is
in Vue Version 3, the Options API is
still here to stay. But alternatively, we have a different approach
we can also use, which is called the
Composition API. The Options API is something
you may be familiar with if you've built with Vue
Version 1 or 2 in the past. It's a traditional way of
building a Vue.js applications. The good news is in Vue 3, it is not gone anywhere. The Options API look like this. Inside the script
and export default, we had various sections
such as a data section. This contained any
component data or state, which you can think
of as variables. There's also methods. This is where we can add
our JavaScript functions and we can call them
as we need them. Of the sections not shown here include computed
life-cycle hooks, watches, and props. Again, all still valid
to use in Vue 3. However, for this project, I'm going to be using the
Vue 3 Composition API. The Composition API
allows us to write code more like
regular JavaScript. This is just like a regular
JavaScript variable, a timeout, and also a function. This can now be wrote
inside of Vue.js apps, replace this just like
the Options API inside of an export default and then
into a setup function, which is highlighted here. Things in here such as variables and functions can be made available to the template by returning them down
on the bottom. It is also worth
noting that we can use the setup function alongside
the existing Options API. Here we'll have the
data section passed in, but any other section from the Options API
can also be added. That is a lot more to it too, such as we can organize the
code into separate files, and reuse the path where needed, and many other things which
we'll use as we need them. Alongside regular
JavaScript, we can also make use of Vue reactivity. Here we import a ref from the Vue package and wrap all
variables value with it. A ref is used to turn the contents into
a reactive object. Meaning, any parts of our
application which relies on it will change if the
data inside changes. Basically mean no
components are kept up to date with any data changes. We'll also come back
to this one very soon. Along with ref, we
can also import from the Vue package things like computer properties
and also watches, which are available
with the Options API. Finally, we also
have a shorter way of writing composition code, which is the script setup. On the left is the similar
example to previously, where we have a setup
section and return any Vue we want to use
in the template or HTML. Then on the right is
the exact same thing using scripts setup. It adds a setup attribute to our script tags to
make the code shorter. Also, notice here we don't need the returns section
because all of the data we want to use in the template is automatically
made available. The script setup will be
the approach which we use in this class project
since it's cleaner, shorter, and also has better
performance internally.
24. The NewPizza Object & Data Binding: Let's now head into
the NewPizza.viewfile. We can begin to add
some JavaScripts making use of the scripts
setup which we just looked at. We can add a NewPizza objects, which will be the
structure for all of our pizzas in
our application. Just above the template add in the script tags place in
the setup attributes. Then as we've just seen,
we need to import our ref, and we import this
from the view package, which is in the node modules. Then we can create
our NewPizza objects, which is wrapped in this ref. This ref is going to create a reactive object which we can use throughout our application and the state is kept updated. Let's start this inside
of a constant or a variable called NewPizza, set up our ref as the wrapper. Then inside we can pass in
any type of data we want. This could be a string,
it could be an array. But for our use case,
we need to create an object that have all
of the Pizza properties. These properties consist
of the Pizza name, we can say Margherita. But just for this, I'm
going to put in Eg. The reason for this is
this is going to be the initial state of our form. I'm going to go into our admin, let's just take a look
and we see this name. This is going to be
linked to our object. The name property here using V-model will be
linked to our name. We'll also link the description
to the next property. When we first log this form, the text inside of here will reflect what we see just here. This just gives
the user a prompt exactly what they need to
write to create our NewPizza. Next, a description. Again, we'll give an example of a delicious tomato base pizza
topped with mozzarella. Separate by comma, we can
then add our options, which is going to be an array. Placing these in an array
will allow us to create different options,
such as our size. Each one is going
to be an object. We're going to have two objects, just like this side-by-side. Each one of these is going
to have the size property, let's say nine inch for
the very first one, and also a price. The same for our second object, but this one is going to be for a 12 inch pizza
and also a price. Remember that all
of these values are just starting values. All of these object values
can be replaced with the values from this
new pizza form. They will be updated before
we add these to the database. To link our form inputs
with our object properties, we're going to make
use of the model. The model allows us
to create something called two-way data binding, which as mentioned,
means the values from our object will show
inside of the form inputs. In any form inputs we type, we can then update to
override these form inputs. This will be reflected
inside of our object. To do this, let's
start with the name. Go down to our form inputs, where we type the name in. We can make use of v-model passing this in as an attribute. We want to link
this to our object, which is called newPizza, and we can access the name
property, so newPizza.name. Let's copy this. Go
down to the text area, and we can link this
to the description. After the description,
we then have options, and we need to link these to each one of these array values. Remember, arrays begin at
index number position zero. This one will be zero, and this one will
be index number 1. Let's go down and we
can add these options. We've got the size number 1, paste this in, newPizza.options. Select the first value
with the index number of zero on the property
called size. Let's copy this, paste
this into the one with the ID of price number
1, changes to be price. Then down to our option
2 into the size, this is index number 1. Again, index number
1 for the price and also change the object
property to be price 2. Now, if we save
this to our form, we can see all of the initial
data inside of each one of these and you can see we also have the
option set correctly. We've got the nine inch in
size and also the 12 inch too. Also see the two-way
data binding and check that the object is
updated from this form. We can now go into
our templates. Anywhere inside of here, we can output the
value of newPizza. We can see this at the top. Let's remove anything
from these inputs. This is now updated. Same for the
description this works. Let's try to update the price. Also our second
option is linked to. We can now safely remove this and move on
to the next video. We'll take a look at refs in more detail and how we can
add items into the basket.
25. Ref’s & Adding To Basket: Previously inside this
new pizza component, we briefly looked at
something called a ref. A ref is a wrapper for a value. In our case, the
value is the pizza. In the upcoming video,
we'll also use a ref as a wrapper or basket, which a user can add pizzas
to before they purchase. It does this by
converting the value into a reactive object
and the benefit is each time this reactive
object is updated, all of the components
which rely on this data will also
be updated too. Let's go into the menu
view to begin to work with these and take a look
at refs in more detail. The menu view outside
the template, create our script with
the setup attribute. Create our import, just like
we previously looked at, where we'll import the ref
from the Vue.js package. We also need to
create a constant or a variable for our basket. Wrap this inside of a
raft to make the contents reactive and set the initial
value of an empty array. We also need to
create a function which is going to
update the basket. Let's call this addToBasket. As we mentioned
earlier, when we make use of script setup, this will allow us to
directly access any of these variables or functions
inside of our template. Let's go down to our template
and locate our Add button, making sure we're
inside the menu side. We can list now for the click using @click and set this up to trigger our function
which was called addToBasket. Check
this is working. We'll place in a console log. Any string of text is fine. Jump into the console. Click on the button and
our text is now updated. Just before we add some
more code to our function, I quickly want to
go back to our ref and see how this
works in more detail. For this, I'm going to create
a simple object called user with the name property and also a likes property too. You may be wondering
why I've just typed out a unrelated user
object inside of here. Well, the contents of this
object are not important. The important thing
to note here is JavaScript objects
have properties. In our case, we have
the property called name and the property
called likes. Remember earlier when I said
when we make use of a ref, Vue.js behind the scenes
turns the content inside into a reactive object. As we've just seen, any type of object has these properties. Since our basket is turned into an object
behind the scenes, it also is given a
value property and this value property
is how we can update or read the
content inside. Let's quickly type this out and I'll show you exactly
what we mean. For now, we'll just
comment out our basket. We'll create a knew baskets, which is not going to
make use of a ref. This is just going to
be a simple object. Again, just to reinforce this, behind the scenes
our basket is turned into an object just like this and then given a value property so when it comes
to updating a ref, just like our basket, not only do we update it by the variable name
such as basket, we also need to update
the value nested inside. As an example, when we call
our addToBasket function, we'd first access our
variable or constant name, then the value inside. Or we could use
something such as the push method to
push a new object. The name, any content
is fine, the price. This is all just example code, it doesn't matter size of 12. Since this is a basket, we also need to know
the quantity too. To check this
works, check we are updating the value inside, we can go down to
our template and output the value of our basket. Let's save this.
Over to the menu. We can see we have the value
property which is empty. Click on "Plus" and we
don't see this is updated. However, if we update
this to make use of a ref , uncomment this out. Click on the button. We
can see this object is now reactive and any update to our basket is now
pushed to our template. One key point to note
here is when we're making use of the basket
inside of the template, just like we are here, we don't need to access basket.value. This is automatically
done for us. However though, as we can
see inside our script, when we access or update
any one of our refs, we do need to make use
of the value property to update or to read the
data nested inside. Of course we don't want to
output the basket like this. What we want to do is to go down to our basket section and output the content inside our table
without our table row. We can add inside
the table body, place this back
inside and then we can loop over our basket
with a Vue.js V4 loop. Inside the brackets we can
access the variable name of item and also the index
number on each loop. This is in basket, and then Vue.js, when looping also
needs a unique key. Now we don't have any great
unique key we can use. But as a temporary
measure, we can pass in the index number. Later on when we have the pizza stored inside the database, we'll update this
key to make use of the unique ID for each value. Now, each pizza
inside the basket is stored inside
the item variable. We can update the quantity, so item.quantity,
the pizza name, this is item.name, the size, item.size, and also the
price down at the bottom. If we wanted to,
what we could simply do is add item.price. But since we can order
multiple pizzas, we're going to
multiply this price by the item quantity to display
a total for each line. Save. Make a little
bit more space. Now each time we
add a new pizza, the basket is updated and
displayed inside of our table. Next, we'll improve on
this by looping over the pizzas and adding the exact
pizza name to the basket, rather than the sample data
which we currently have.
26. Looping Over Pizzas: Currently, as we
know, we don't have our database set up yet
to store our pizzas. For now, as a temporary measure, we'll create a ref, which we can push our pizzas to. This will then allow
us to loop over these pizzas and display
them inside of a table. For this, go into
the MenuView file, and then up at the
top we'll create a new constant called allPizzas. allPizzas is going to be
surrounded inside of a ref. Well, the initial value
will be an empty array. Then over to the new
pizza.view components, which is inside the admin. It saves a little bit of time. What we're going
to do here is to select the full pizza object. This is the demo object just
here inside of the ref. Copy everything including
the curly braces. We're copying this
because this is going to give us the
consistent structure, which we need to add new
pizzas to the database. Back into the menu view and inside of the
old pizzas array, paste this in twice. Make sure that each one is
separated with a comma. Let's just change this
around a little bit. We'll remove the Eg. Margherita. The second one
Pepperoni, remove the Eg. We'll say topped with
mozzarella and pepperoni. Just increase the price to make this a little
bit different. Now, this can be looped
over in the menu table. They display each
one of the pizzas. Now to the template. Have this menu and
then the table nested inside the table body. We'll do exactly
the same as what we did for the basket where we'll create a v-for loop and place in the data
where we need it. Back up to the menu
section table body v-for. Inside the brackets,
we can select the pizza and also
the index number. This is in our old pizzas array. Then place in a key, which is going to be the
index number for now. Just like we said
with the basket, we'll also come back to
this later on and make use of the unique key which
the database will provide. Great. Now, we have
access to all pizzas and instead of displaying the
hard-coded pizza name, open up the double curly braces, and we can access our
objects, which is pizza.name. The description,
pizza.description. Let's say this and
over to the menu. There's our two pizzas. We have our Margherita
and the pepperoni, which matches our ref
from our script section. We're still not quite done
with this one though, because we also need to
add the pizza options. We need to make the pizza
size and also the price more dynamic in much of the options which will have
for each one of these pizzas. For this, we again need
to go down to our table. Here we have this table row, which is for each
one of our options. We need to create
an additional loop inside of here to loop through our two
options, so v-for. The variable value of option and we'll also take it in the index. We need to loop over
our pizza object, which is the one from
the initial loop. We'll need to
access our options. The key for this one
passing-over option. Then inside the square brackets, we can select the first or the second value with
the index number. For the first loop, this will
select the first option, and the second loop will
select the second option. For the option size, double
curly braces, the option, and the size property even in the currency
symbol, the option.price. Let's test this out
over in the browser. We now have our loop
creating our two options, and each one has the unique
size and also the price. Good. We're making good
progress here, but now, when we want to add each one of these pizzas to the basket, we also need to pass
this pizza information to the function. This button which
you have here not only do we just need
to call addToBasket, we also need to add the
brackets and pass in the pizza and also the
option up to the function, where we can receive
these values, so the item and the option. Now, instead of having
the name as a string, we can pass it in the
variable name of item.name. The price, which is stored in the option, this
is option.price. The size, option.size. Let's try this out. Let's try
the nine-inch Margherita. This looks fine. A
12-inch Pepperoni, this all looks like it's
working perfectly fine. Next, we'll handle updating
the quantity inside of this basket if the same pizza
is selected multiple times.
27. Checking For Duplicate Basket Items: At the moment, we'll add a
new pizza to our basket. Everything works fine
but we start to have a problem if we add the
same value multiple times. For example, if we try to add the nine-inch pizza
more than once, we already have this
at the very top rather than increasing
the quantity to be two, we get a new line item
down at the bottom. This is the same for
any one of our pizzas. Instead, what we want
to do rather than add a new line item is to increase
the quantity each time. For this, before we push our
new pizza to the basket, we first need to check if the new pizza already
exists inside this basket. To do this at the very
top of our function, we can access our basket, the value since this is a ref, and make use of the
JavaScript find method. The find method will
run a function for each value inside of our basket. If there is any pizzas
existing inside the basket, we'll store this inside
of a variable called pizza and then we can work
with each pizza individually. What we want to do here
is we want to check if both the pizza name and also
the pizza size is a match. Remember if we go into our
menu such as the Margherita, we can add two different sizes. We can have two
separate pizza names, but these aren't a match. We also need to check
the pizza name and also the option value too. The JavaScript find method will return back the
very first value, in our case, the pizza, which matches a
certain condition. We want to return true if a
certain condition is met. In our case, we want
to check if the pizza.name is equal
to the item.name. This is checking if the pizza which is currently
in the basket, has a name, which is equal
to the name of the pizza, which we are trying to add. There should be a
triple equals and using the double ampersand we also need to provide a second check. The second check is to find
out if the pizza.size, this is the pizza again, which is stored
inside the basket. We want to know if this is
equal to the option.size. Again, this is the
option size of the pizza which we
are trying to add. If both of these are a match, this statement is true and this find method will
then return the pizza, which we find in the basket. We can store this inside of a constant or pizza exists
and before we go any further, we'll check by login
this to the console. Log the value of our
constant pizza exists save and over to the browser. Jump into the console so we don't have any pizzas
crony inside the here, plus, this is undefined, different values
is also undefined. But if we try to click on
a pizza a second time, we get returned
back at the pizza, which is a match for the basket. We see this is a nine-inch
pizza let's try with the 12, this one also works too let's
just try the pepperoni. The very first time is undefined and if we already have
this in the basket, it will then return back
the pizza which it finds. With this information, what
we can do now is placing an if statement and we can check if our pizza is
found with this constant. If it does want to do
is to access the pizza, select the quantity property, and increase this by
the value of one. Also, if this is true,
we want to return, alter this function since
we don't want to add an additional pizza to our
basket. Let's now try this. Give this a reload. Our
first Margherita our second Margherita now increases the quantity. Let's
try pepperoni. Two new lines and a duplicate and each time I click
on a duplicate, the quantity is increased
by the value of one. Next, we'll also come back to our basket and stick
with the quantity subject by wiring up our increase and our
decrease buttons, along with a function to remove a pizza from the basket too.
28. Removing & Changing Basket Quantity: As we discovered in
the previous video, when we add any one of
our pizzas to the basket, we now have all of our pizzas
shown over on the right. We also see the quantity
which is the default of one. We also have these buttons to increase and decrease
the quantity. This is what we're
going to work with now. Head over to the menu
view and jump down into the template section and scroll
down to the basket area. Inside baskets, we
have our table body, which is creating our loop
for all of the pizzas. Here, inside of the
loop we have access to the item which you want to
change the quantity for. Then here we have our buttons to increase and also
decrease the values. For the first one,
which is the decrease, we can add a click listener, which will then
trigger a function. We'll pass this item to the function and then
change the quantity. Inside the open button, @click, and we'll call this
function decreased quantity. Pass in the item so
we know which item to reduce and then go down
to the second button, which is to increase. We'll call this
increaseQuantity. Again, this also needs access to the item which
we want to change. We'll start with this increaseQuantity
function. I'll scroll up. Remember, if we take a look
at this push method here, we can see the structure
of our baskets. All we need to do is to increase the quantity property
on the object. Create a function,
increaseQuantity, take in the item which is
passed to it, select the item. We only want to work
with the quantity field, and we'll increment this
by the value of one. Next, the function to
decrease the quantity. Also takes in the
item, item.quantity. I will deduct this
by the value of one. This is the first
thing which we need to do for the decrease function. The second thing is to check
if the quantity is zero. If it is, we don't want a line item shown inside the basket with the
quantity of zero. Instead, we want to remove
this from the basket. Of this we can place in an
F section to check if the item.quantity is equal
to the value of zero. If it is, we want to remove
this from the basket. If we wanted to,
we could place in the code inside of
here to do this. But instead I'm going
to outsource this to a standalone function which is going to be called
removeFromBasket. We'll also need to take in
the item which we want to remove and then create
this down at the bottom, the function removeFromBasket, pass in an the item which
you want to remove. First, we need to
access our full baskets and since this is a Ref, we access the value property. Then we can use the
JavaScript splice method. Splice is going to
remove an item from our array by first person
in the starting position. So we need to grab the
index number of this item. Let's do this by accessing
the basket.value. This is the full basket. Then we can grab the index
number with indexof. We need to know the index
number of our item. This is just regular JavaScript. It's nothing to do with Vue js. We find the index position of our particular item inside of this array separated by a comma. We only want to remove
one single item. Now, let's give this a save
and check this is working. Click on any pizza. If
we keep increasing it the value will increase
too. Let's try plus. It works fine. Negative,
this will drop down. Then once we get to zero, this item should be removed. This is some good
functionality which has been created with pretty
small functions. Next, we're going to stay inside this basket and get to work with updating the order total
using computed properties.
29. Computed Properties: If you've used view
dress in the past and made use of
computed properties, the good news is they
are here to stay. They are still fully
supported in view free, both when using the
composition API and also the options API. They'll be an ideal use
case for us to calculate the cost of all the pizzas
inside the baskets. At the bottom of the basket, we currently have this
hard-coded value, which is available
inside of the menu view. Let's scroll down and find this. This is near the bottom
of our template. We could use regular
functions to calculate the cost
inside this basket, but the problem with a
function is it will only run once each time we call it. If we instead write
this calculation inside of a computed property, anytime the data inside changes, in our case the baskets, it's value will be
recalculated for us. To do this, go to
the top of the file. We need to import computed
from the view package. Computed acts as a wrapper. If we break things down, we can first create
an anonymous function to calculate our baskets level. Down at the bottom
of our script. We'll first create
a function which doesn't need a name
at this stage. What we're going to do is
create a variable called total cost which will
be initially at zero, and then we can loop
over all baskets and push the quantity field
from each one of our pizzas. That is total cost. Baskets, again, this is a let, so we use the value loop
over with for each. For each, we'll run a function
for each value inside the baskets and store
each one inside of item. Then we can grab our empty
variable called total cost. Use the plus equals operator to add a new value to our
total cost on each loop. We don't just simply want to grab the item.price
and push this to the total because sometimes
we can have multiple pizzas. For this, we need to multiply the item.quantity by the price. We can access this total
cost outside the function. We need to return this. And then outside the function
creates a variable or constant to store
our computed value. We'll call this one the total. As mentioned, computed
will act as a wrapper, so we need to place it
inside our function. Cut this out, paste this
inside of computed. Now, since we have
computed as a wrapper, each time the basket or any
other data inside is updated, this function will rerun and the total variable
will be updated. This total can now be used
inside of our template. Jump down to the bottom. You add the total cost in, add an item as much as the
price since we only have one. Increase the quantity. This is also now doubled since inside of our computed value we multiply the item
quantity by the price. This all now appears to
be working correctly. Later we will improve
on this by creating a re-usable currency filter to make sure the numbers
are rounded correctly.
30. Composable Files: In the script section of our MenuView which we've
been working in recently, if we take a look from
the top to the bottom, this section is starting
to get pretty big. To help with this, we can move the contents into
separate files, we can use as many of
these files as we want to, which will keep things
really organized. These are referred to
as composable files. To keep these organized, we're going to
create a new folder called composables
to keep these in. These will live directly
inside the source and go alongside things like our
assets and our components. Click on the Source,
a New Folder, Name Of Composables,
and if you want to, we can change this folder name, it doesn't need to
be composables, this is just a
naming convention. Another naming convention is
the files which are inside. These tend to have
the prefix of use, but again, this is
completely optional. For those unrelated
to our content, we would have files
such as useBasket, usePizza, and useOrders, which are all regular
JavaScript files. Let's start with a
file called useBasket. Place this inside the
composables file, useBasket, and this has
the JavaScript extension. This file is going to
export a function, so export default, a function called useBasket. The contents inside of this
function will be all of the basket-related
functionality inside of our MenuView or any other
files for that matter. Back to the MenuView, let's take a look for all of the basket-related
functionality. We need the basket,
let's cut this out, paste this inside
of our new file. We don't need the
pizzas, this will go into a different file, addToBasket function,
we can take this. We also need the
increaseQuantity, decrease, removeFromBasket, and
also the basket total, which is it's computed value, cut all this out and we should just left with our pizzas, paste this into our
composable file, making sure everything is wrapped inside of this function. Since we're not
making use of our ref and our computed
section inside of here, we can also bring over
the imports or copy this. We can see we're
still making use of our ref inside of this file, but we couldn't remove computed. Then, import these at
the top of our file, just outside of the function. Now to make use of
any functions or any of these variables
in different files, we first need to
return this back from this function down
to the bottom. Still within the closing
tag of our function, create our returns
section and this needs to return back any one of our functions or variables which we want to
use in other files. We effectively need everything from here, we need the basket, need the function
called addToBasket, we need the two functions
to increase and also decrease the values,
we need the total. But one thing we
actually don't need at this stage is removeFromBasket. This is because the only
place this is called is within this
decreaseQuantity function, so we don't need to call
this from any of the files. This now groups together all of the related functions
and variables. Remember, we exported a
function here called useBasket. To access the contents inside, we can go over to our MenuView and import
this into our file. Import, useBasket.
Using the add symbol, we can jump into
the top level of our source and then jump into the composable folder and then the filename
called useBasket. Next, using JavaScript
destructuring, we can store all of the
returned values from these functions into variables. The basket, the functions
of increaseQuantity, decreaseQuantity, we
need to addToBasket, and also the total. I was selecting these from
our useBasket function, which we imported just above. All of these constants
are now available to use inside of our template. All that's left to do
is to now save this, go back over to our project and check everything is
still working correctly. In fact, just before we do this, we need to actually
call this function for these to be available. Over to the project, add
these to our baskets, the increase and decrease
functions working. We can remove these, and also
our total is working too.
31. What Is Firebase?: So what exactly is Firebase
and how can we use it? Well, Firebase is a service provided by Google for websites, applications, and also games. Many of these need services
such as authentication, storage, hosting, and
also a database to. Firebase provides all of these
in one single application. I'm going to make use of some of these inside of our project. We'll be making use
of some of these, including the Cloud Firestore, which will be our database, which stores our
pizzas and our orders. The cloud Firestore is
a real-time database, meaning when any of our
stored data changes, the application is notified. A use case for this could be if the admin was to remove a pizza, and then the menu would
also be updated too. Also, if you have multiple apps, maybe your website
and mobile app, the database could be
shared and kept in sync. We can also set up rules and roles for security purposes too. But more on this later. It will also provide our
authentication system, providing sign-in, login, sign-out,
forgotten password flow, along with multiple ways
for users to sign in. We'll be making use of an
email and password login. For there is other services
available such as Google, Twitter, or Facebook login. In addition, there
is also lots of other Firebase services which will be adding
such as analytics, cloud functions, and storage. So go ahead and sign up for an account if you've
not yet done so. The free account
is all we need for testing and building
our project, and the next, we will set this up and link to our project.
32. Firebase Setup: Once you've created an account
and logged to Firebase, head into the console
area and you will see something which
looks just like this. From here, we can
create our new project. We need to give this
project the name, well, will be Pizza Planets. You can call yours just
Pizza Planet if you want to, but this is the third version of this class so I'm
going to call mine V3. Click the checkbox
and then continue. Google Analytics, I'm going
to say no for this project. Then give this a few
moments to complete. My project is now setup. Let's continue on and then this will take us
inside of our project. From here we can get the
instructions to add Firebase to our app for either iOS, Android. But in our case, we'll
click on the web-link. If things do look a little bit different at this
stage, don't worry, these websites do have
a habit of changing, but it should be a
similar setup process. Now we need to register our app by giving this a nickname. I'll just keep it the same
way Pizza Planets Version 3. I'm not going to set up any
Firebase Hosting for now. Register. Then once
this has completed, it will give us some instructions
for setting this up. The first thing to do
is we can use npm to install this as a
node modules package. Let's copy this link, which
is npm install Firebase. Let's close down the
server. Paste this in. Now we have some reasons why
it's been really slow today. We can just close
this down and also restart the server
with npm run dev. We can confirm this
has been installed by jumping into the package.json. They will have Firebase
as our dependency. Back to the Firebase website. The next stage after installing, is to copy over
some instructions. I will just copy this over
and we'll take a look at what each stage does
in just a moment. This will keep us
organized by placing this into a new JavaScript folder, which sits directly inside the source folder,
create a new file. The Firebase.js. Paste this in. The first thing we
do is to import and initialize App functions, which is provided by Firebase. We then have the
Firebase config object, which is conveniently populated with all the information
for our app. Then at the very
bottom, we make use of this initialize App
functions just here. Then pass it the
configuration details, which then gives us a
reference to our application. Now we have the app reference. We also need a reference to our Firestore database so we
can add and remove records. From this, we also
need to import something from the
Firebase library. Up at the very top, we'll create a second important where we're
going to import something from the package,
which is Firebase. But this time, rather
than forward slash app, we need to import this from
the Firestore section. The function which you
want to import from this section is
called getFirestore. GetFirestore can now be used to get a reference to our database. To do this, we pass in our app variable.
Just this one here. This inside of a
constant called Db, which is short for database, called getFirestore.
Pass in our app. When working with fire store, it groups together our
data into collections. In our case, we'll have
a collection for pizzas, will have a collection
for orders, and also we can have
one for users too. To work with Collections
will need to also import it from the
Firestore package. Back to the top, get Firestore package,
import collection. We can use this to reference our collections by
passing in two things. The first one, we needs pass the database reference
which you have stored just above inside this constant. Second, is going to be the
name of our collection, which we currently
haven't created yet. This one is going to
be called Pizzas. This now gives us a reference to a unique collection which we can use to read documents
and also add new wants too. To make this available
in other components, we're going to export
this as a constant. The constant name
will be dpPizzas&Ref. Let's copy and paste this in. This second reference is
going to be for our orders. DbOrder&ref. This will make
use of the same database, but the collection
name of orders. This will allow us to
import these references into any other
components are files. We could do things like
read the contents or add new data. We'll
look at this next. The final step for this video
is to tell Firebase we want to use the cloud Firestore
and also authentication. Let's go into the
project overview section inside the browser. We can continue on
to the console. We don't need any more
data from this view. This gives us a chance to add various products to Firestore. We can do things like keeping
track of performance. We can add analytics for our use case at
the moment all we need is authentication and
the cloud Firestore database. Let's start with the
authentication. Get started. This is going to
let us sign-in with various providers such
as Google and Facebook. But I'm going to make an
email and password system. Click on this will enable
email and password. Now I'm not going to
enable the email link, we'll just keep it as a straight forward email and password. This is all we need
to do for now. This is currently enabled. Back to our project overview. You can see we've got the
shortcut now to authentication. Next we'll add the
Cloud Firestore, it also appears in the sidebar. Create our database. We'll begin in test mode and we'll look at security
rules later on. But for now this will
give us full access to read and write to our database. This feature should only
be allowed for testing, and we will look at how
we can improve this later on. Click on Next. Choose a location if you want
to and enable the database. Later on we'll enable some security rules to keep
our database more secure. Good. We now have a
authentication mode setup and we also have
an empty database. With this in place, next, we'll take a look
at how we can add some new pizzas to
this empty database.
33. Adding Pizzas To The Database: Now we have the setup
work out the way, we can use the dbPizzasRef
which you previously set up along with our
order reference to work with our database. We'll start by adding
the new pizzas to the database in the
new pizza components. For this, we need to
import two things. So let's jump into the
new pizza component, which is in the Components
folder and the Admin. At the top, create our import statements
where we'll import something called addDoc, which is from the package name, which is Firebase/Firestore, which makes sense because this is a database-related function. The second one we need to
import is our dbPizzasRef. The file path has conveniently
been populated for us. Both of these will
be used inside of a new function to add our pizza. Let's go down to the
bottom of our scripts. This is going to be async, create function called Add. Since we're working
with a database and we could have
things go wrong, we need to place this in a
try and a catch section. Catch takes in the error, which we can store it
in the e variable. For now we'll work inside
of the try section. From here, we can
call our function, which we just imported
called addDoc. Inside of here, we need
to pass in two things. The first is the collection
which you want to add to, which is stored in
our dbPizzasRef. Remember, this will reference a collection called pizzas. The item which you
want to add to the database is going
to be our newPizza, which is this object just above. Since this is stored in a ref, we need to access the value. We can now call
this add function when we click on the Add button. Remember inside of the application
and the admin section, we have this Add button. Jump down to the
template. Here we are. We'll listen now for a
click. The function name of add will prevent
the default behavior, which will stop the page
from reloading and losing all of the data
or form and also, since we don't know how
long this is going to take, since we are pushing some
data to an external server, we're going to add await since we can wait on the data coming back before progressing
to the next stage. And we can use this
since we've marked our function as async. Just before we go ahead and
test this and make sure this pushes our newPizza
to the database, we'll add some
error handling and also a message to display
inside the browser. This message can be stored
inside of a constant. Just above our newPizza, const message, store
this inside of a ref, since we'll be updating this. Initialize this as
an empty string, and back to our add
function where we'll update the value
of this message. Since we are making
use of async, await, any code which runs
below will only happen once we get the information
back from our database. On this next line, we'll know if this has been a success or if there's
been a failure. Since we've wrapped
this in a try section, we'll know that
this code will only run if there is a success. We'll add the failure code
inside of the catch area. Below, access our message.value, which will be a success. Open up the backticks
and we'll say Pizza, place in a variable name. We'll place in a variable
so we can access our new pizza and the name, newPizza.value.name
has been added. And then if there's
been an error, jump into the catch area, we'll access our
message.value and set this to a string where there was an
error, add in the pizza. We next need to
output this message inside of our template. I'm going to do this
at the very bottom, just below this button,
inside of a span. I'll put our message
and the last thing to do is to test this
out into the browser. Remember our database
is currently empty. Inside Admin, we'll go
ahead and add a Margherita. We're going to move
the example code. Click the Add button to
trigger our function. There we are, things are
starting to look good. We've got Pizza
Margherita has been added into the
database and refresh. This is looking promising. We
have our pizzas collection, which we created inside
of our Firebase config, which is this one just here. We've then made use inside the newPizza of a
method called addDoc, and it's addDoc method then
referenced our collection, which is Pizzas, and the value
which is newPizza.value. Into the database, this
Pizzas collection has a document's ID which is unique and then all of the
contents of our new pizza. Let's try one more, Pepperoni. Add this. There we go. We know how to document references and if we
click on this one, this one is now the Pepperoni. Why not? We'll add one more
back to our new pizza form. Vegetarian. Say mozzarella, peppers, onions, mushrooms. It doesn't really
matter at this stage. We'll update the price, say eight and 14. Add this. Then we also, we've achieved our objective for this video, which is to push a new
pizza to our database. But just fall wrap things
up if we take a look, this text just
here, we also need a little space into the
left of the message. In the newPizza file,
go down to the bottom. Add a style section. We'll give this a class or span, a class of message, access this and set some
margin on the left, 1rem should be fine. We need to add a new
pizza so we can see this. We'll go for meat feast. Change the description,
pepperoni, salami, beef, and ham. Change the price. There we go. Coming up next, we'll see how we can retrieve these pizzas from the database and display
these inside of our menu.
34. Getting Pizzas: To retrieve pizzas from
our Firestore database, Firebase provides a
function called getDocs. Also since we want to make
use of these pizzas in both the menu and also
the admin section, we can create a shared
composable file to do this. Let's create a new
file in the sidebar. In the composables, we'll create a new file
called usePizzas.js. This will follow a
similar pattern to the other composable
which we created. We're going to export
a function which we can import into any other file. Export default function called usePizzas and create
the function body which is going to
contain all the code which you want to
make available. Outside, we can add our imports
at the top of the file. We need to import
the ref from vue. The reason we need this
is because we need a constant which is going to contain all of the
pizzas locally. Const allPizzas is equal to a ref and this will be an
array of all of the pizzas. Now for actually
getting the pizzas, we need to import some
items from Firebase. The first package we need is one we mentioned before
which is getDocs. This one is from
firebase/firestore. Then we also need to import
the reference which we created in the Firebase
file to have access to this pizzas collection
so we need to import the dbPizzasRef from
this Firebase file. Import the dbPizzaRef
from this file path. Then just below where all pizzas are constant, we'll
create a function, which is going to
be responsible for retrieving our pizzas
from the database. This function will be marked as asynchronous since we need
to await on the pizza data coming back before
we can actually use this function called getPizzas. Then we can call our
method from above which we just imported called getDocs, pass it in our
collection reference, which is dbPizzasRef. We're going to store
the return value in a constant called docs and we'll also await
this data coming back. Then run this
function and actually get the pizza from the database. We have a couple of
different options. We could trigger
this manually by calling getPizzas or we can trigger this when
a component mounts using the onMounted
lifecycle hook. From the vue package, we'll do this with onMounted. Then as soon as this code or this component is
mounted to the DOM, onMounted will be called, which will then call our
function called getPizzas. This avoids needing to call this function manually
and it means it will do this automatically behind the scenes as soon as the
component has loaded. If we go over to the project
and jump into the console, we can test this out by doing a console log for the return
value inside the docs. Refresh and we still
don't see anything inside the console because this code isn't currently imported
into components. The onMounted lifecycle
hook is not called. So let's do this but first, we'll return the values
which we need to import. All we currently need is
this reference to allPizzas. We'll start by
importing this into our MenuView which is
in the view section. Then just like we did with
the useBasket composable, we first need to import this, so we'll import usePizzas
from our composable file, and then using this
structuring we'll import our allPizzas
reference from this function. Const allPizzas is equal to the return value
from usePizzas. We've now seen an
error since we are doubling up on this
allPizzas reference, we are importing this
from our composable. We also have this
example code just here. We no longer need any of this. We can remove this section. With this being the only
code which makes use of ref, we can also remove
this from the script. If we reload, we don't see anything logged to
the console just yet. This is because for this to actually mount and to
call our function, we need to go into
the menu component. Select this, we then see
a log inside the console. Open up this object
and there is a lot of information which is returned
back from the database. A lot of this
information we actually don't need at this stage, but one thing we're interested
in is this doc property. Open this up, and inside
is an array containing each pizza documents
which we have created. Let's take
a look inside. Again, there's lots
of information here. We can see the document ID. We can go into documents and data and drill down
into the data values, including the name, the
options, and the description. What we can do now
is to loop over all of the values
inside the docs. Unfortunately, we don't
need to drill down into all of these
levels of our object. Instead, we can call
docs as a function. This will extract all
of the fields which we need and make these accessible
inside of our script. Jump back into our composable. We can move the log, access our returned documents and
call it for each loop. For each, we'll run a function for each
one of our values. In my case, I currently
have four pizzas stored. Then to filter out all of
this unnecessary data, I'm going to create
a new object with a simpler structure,
so const pizza. Inside here we can structure this new object with only
the data which we need. As we have seen, each one
of these has a unique ID. We can see this if
we close this down, this ID, so we set the ID. To access the pizza
information on each loop, we also need to pass in a
variable to our function. In the first loop, doc will
be equal to our first pizza. On the second loop, this will be our second pizza, and so on. We access this and
select the ID property. Then we need to pass in the
data which would have seen, which is stored inside the
documents,.data.value. Now we need access to
all of these fields. As mentioned, we don't
need to drill down into all of these fields instead, what we can do is to access our documents and call
the data function. Using the JavaScript
spread operator, we can also extract the values
which we need in places directly inside of our pizza
object alongside our ID. Finally, on each loop, we
want to push the value of this pizza to our
allPizzas reference. Just blow this object, access allPizzas.value, and then use the
JavaScript push method to push each one of our pizzas. This is everything we now need. Back over to our menu file. Remember we've imported all of the pizzas from our composable. Also, this variable name is also the same as we've
been using previously. Now if we save this and reload, the table is now populated with the pizzas from our database. Another thing we can
now do since we have access to a unique
ID for each one of our pizzas is to change the unique key for each
value inside this loop. Rather than making
use of the index, we can access our pizza.id. We no longer need this index, so we can just
reference our pizza. The same for the
options. The options need to consist of two things. The first one, again is the pizza.id and since the
pizza has two options, we'll also add this
to our option.size. Again, we can move the
index and there we go, everything is now complete. Next, we'll continue on with a similar theme by also making
use of these pizzas inside the admin section and
also we'll discover how we can delete these
pizzas from our database.
35. Listing & Deleting Pizzas In Admin: As we discovered in
the previous video, if we go into this admin link, we also have this
menu components which we created earlier. As we also did with the
menu in the previous video, we now need to pull in
our pizza items from the database and display
these inside the admin. Also, we'll make sure
that this red cross, which we have listed next
to each one of our pizzas, also removes this item
from the database. Let's jump over to our
use pizzas.jsfile. This is the composable which we set up in the previous video. We need to import two additional
things from firebase, in particular, the
firestore package. What we need is something called delete doc, which as it sounds, is going to delete an item
from a collection and also the doc and we're going
to use doc to refer to any particular document which you want to select. Then this is passed to delete
doc to actually remove it. We'll create a new
function inside of this object just
below onmounted, this will be async
called delete pizza. This delete-pizza
function is also going to be passed the ID on call it. Because of course, we need
to know which pizza we want to remove and then we can access this doc which we just imported from Firestore. Doc is going to
take in two things. The first is the database
collection reference, which we still have stored
inside of DB pizzas ref and then it needs to
take in the idea of the actual pizza which
we want to remove. This is not actually
responsible for deleting any pizza all we're doing here is getting a reference to a
particular documents. We narrow in this town
by collection and then narrow it down by the ID. We can then use this reference
to pass, to delete doc. On to passes, we can store
this inside of a constant, let's say pizza, and then
pass this to delete doc. We'll await this. We'll await for this operation to complete. Then we'll call our method
above call get pizzas. The reason we want to call
get pizzas once this is completed is because we need
to update our application. We're deleting an item, we then call get pizzas,
which is just here. This is then going to call for all the pizzas which you
stored inside the database, minus one which we've removed. This will then
update all pizzas, which is our ref
here at the top. All pizzas is then returned, document is composable, and this will then
update our menu, which we've created
on the left here, and it will also update
the menu inside the admin. To use this function, we
need to return it also from our composable
delete pizza, then let's open the sidebar, jump into the components, the admin section, and inside of here we
have this pizzas.vue, which is responsible
for this section. Pizzas.vue needs to import the sections which
we need so placing a script tag at setup and
import our composable, which was use pizzas. Use pizzas returned
both of these values, which we can destructure
and store inside of constants so we need all pizzas. Delete pizza, which we can retrieve by calling
usePizzas function. Down to our table section
where we have some dummy data. We've got our margarita in
place and now we can use the table body section to
loop over all of our pizzas. V for pizza in all pizzas, pass in a key and now we have the unique pizza ID from the database so we
can access this. The margarita can
be replaced with the dynamic pizza
name so pizza.name. With this being
the admin section, we don't need all of the
additional information such as the description
and the prices, all we need to see is a
reference to each pizza. We can see this now
over on the left. Each one of these crosses
to remove the pizza from the database
is stored inside of this button so we can add a click listener and then reference our function
which is delete pizza. This also needs to pass the pizza ID which
we want to remove. Now we can access
this with pizza.id. Let's give this a try. We'll remove the meat feast. As soon as we do this,
we see a small issue. If we take a look at the original four pizzas
which we had, this is fine, this is the original
four from our database but then if we go
to our composable, we've removed this
from the database, but then we've called
get pizza once again, which is then getting our
free existing pizzas from the database and also
adding these to all pizzas. Basically, instead of
replacing all the pizzas, we keeping the existing ones and adding the three
additional items. Effectively, this
is not a bad thing at the moment because
we can see that the three additional items doesn't include the meat
feast, which we've removed. That all seems to
be working well. To fix this, this is
pretty simple all we need to do just before we get our new pizzas is to grab
our reference of all pizzas, their value and reset this to an empty array before we
get our new documents. Let's try this.
Refresh the page. Here's our three pizzas, remove any one of these, and this is now all
working correctly. On final improvement
we can make is to add an error message if there
was an error or an issue, delete in the pizza, or even getting the pizzas
from the database. For this, we can create
a new message reference in the all pizzas file. Just underneath all pizzas create a new constant
to store our message. This will be a ref and by
default an empty string. Then inside the get pizzas, we can add some error handling with a try and a catch area. Let's place this in
at the very top at the try section and catch. Catch will take in an error. Then we need to move
all of our code inside of the try section. If you locate the end
of this function, grab everything all the way
up to all pizzas.value. Cut this out leaving
just the try and the catch area inside of here and then inside
the try section, paste this back in and this is the code which you want to
run and try to be successful. But if there is an error, we can then go into
the catch area and update our message. The message.value is equal to a string and we'll say there was an error fetching pizzas. Please reload the page. Just like we did
with the pizzas, we can also update
the message and reset this to be an empty string
each time we run this. The message.value is
back to an empty string. Also the same for
our second function which is delete pizza. The try-block, the catch block, which takes in the error, crop the existing code, cut these and replace place
this in the try block. Reset the message, it's
been an empty string, and then update the message
inside the catch block. It's b string, then
if you wanted to, you could also include
the error message, which we'll get back. But for our simplicity, I'm just going to
create a string which says there was an error
deleting the pizza. Then please try again. Next, so we can use
this message inside of our components we
can return this back, jump back into the
pizzas.view components, import this, and then just
below the header section, create a new p
element to display our message inside the
double curly braces. Add class for styling of error. What will also do as a
temporary measure is to also add some text
inside of here. Any text is fine this is just so we can add some styling and see how this looks without having an actual error message. Down to the style section, where we can add some
styling to this error class. First of all, a color which will be in RGB value
of one at zero for the red, 67, and 67, which will give us
this red color. A border of one pixel, a solid line round off the corners with
some border-radius, and then some spacing inside and outside with padding and margin. Padding 1rem and also 1rem
of margin on the outside, save, test this out. This is how our error
message will be displayed inside the
components but if we were to remove the text
and then leave this as an empty p element
I can see here. Removing this error
text will still show the red border because
the p element is always there regardless of if it has any messages inside or not. To remove this, we can
add a v If statement to only show these elements
if the message exists. If we have a message and its
elements will be displayed, if not, it will be removed from the dom and we're no longer
see this red border.
36. Creating Orders: To create an order follows a similar pattern to
create in a new pizza. Let's go into the used
basket composable, which is inside a
composable folder. Open this up and we first
need to import two things. The first thing,
we need to import addDoc from the
Firestore package, so this one is from
Firebase/Firestore. Then the reference to the
actual orders collection, which you want to push
this to so let's import this from our Firebase file. What we need to do is to
import this DB orders ref. We already have the
basket contents inside of the basket array and
this is pushed to when we add something from
the menu section. Over on the right
here is the basket. Now we need to create
a new function to add the oldest collection
inside the database. Just below this function here, back to this arrow
on the bottom, it doesn't really matter. This will be async, the
function name of add new order and then, what we need to do inside is
to wrap this in a try and a catch block so it can handle any errors, pass in the error. The first thing we need
to consider is that this basket up at the top ,this is currently
stored as an array. But Firebase requires an object, so we can use the
spread operator to merge in the current
items into an object. Let's go into the try
section and we'll restructure our order by
creating a new object. The first property to add is going to be called createdAt, and this will keep
track of the order in which our orders are
placed and therefore, we can deal with them or display them in the correct
order in the admin. But this will just create
a new JavaScript date, install this inside
the database. Then as mentioned before, to grab the contents
from our array, emerge these into our order
to create a pizza's property, which itself will be an object where we're
going to merge in the properties from our basket
and since this is a ref, we need to access the value
just after the order. This is now where we're
going to make use of the addDoc method from Firestore to push to a
particular collection. A collection is our
DB orders reference which we just imported. Call the addDoc method, which takes in the first
parameter of DB audit reference, which is a reference to our oldest collection and then we'll push this particular
order to this collection. Once this has been
done successfully, we'll await this operation
finishing and then we can reset the value of our
basket to be an empty array. This will clear out any
pizzas from the basket, so the user can start again. This is all we need now for the try section and
keep the user updated, we'll create a new variable
to hold a message. The default message
is going to be something like your
basket is empty, so we can see this
or we don't have any pizzas over on
the right-hand side, so const basket text, wrap this in a ref, with the text of your
basket is empty. This will be the default
value and we can update this depending on if the order was
successfully added or not, go down to the function. Let's go in the try section
and at the very bottom we'll access the basket text.value, and update this to
be new strings, something like thank you, your order has been placed. Into the other section, and just like we've
looked at previously, you can also incorporate the error message
if you want to, but for simplicity, I'm
just going to update the basket text to
be a new string, which will be equal to there was an error placing your order. I'm going to say
please try again. We can call this
from our components. We need to return back
our function call and we also need to return back the basket text so we can display this
inside the components. As you order the basket text and the basket is displayed
inside the menu view, so head into here, and we can now import
these from our composable. Here we already
have a reference to our use basket composable, so we can jump into the
brackets and also import basket text along side add new order. Let's start with add new order, which we can trigger
from our button. Go down to the basket div, and down at the bottom we
have this place order button, this now for a click, which
will trigger our function. Now the final step is to
add the basket text and remember by default it
says your basket is empty, so we don't want to
display this if they're already our items inside the basket and for this same conditional
rendering will help. Let's jump up to the top of the basket section,
just below the heading. Let's create div
sections contain this and inside we'll
make use of v-if, to add our conditional rendering where we want to
check if the basket, the length is greater
than zero, i.e. we have some items
inside the basket, so if this is true we'll cut the closing div section and
then go down to the end, just below our buttons place
order, close this off. You can now see if you go
into the basket section, we don't have any of these
buttons or the order total. But all this will
create a new div, which is the v-else section
and this section will only be displayed inside
the browser if there are no items
inside the basket, which is the current state. But this will place inside
the double curly braces, in fact we'll put a p
element in to begin with. I'll put the basket text and we're seeing errors let's
take a look what this is. We have no adjacent v-if so this is the v-l section and
this should be the v-if. We just have this extra
div section which we can remove, make sure
this is correct. We've got this div,
the opening tag, all of the contents, the closing section, v-else. We also need to move this
closing div from before this all looks fine
now just makes sure that after the v-l section, we have two additional closing
divs, so free in a row. Then up at the top of the h3, the div, and then the table. We should now see inside
the browser we have the default text of
your basket is empty, click on any item and now the basket length
is greater than zero. Therefore, we can
display our table. Now let's add a few more items
to our baskets and we can test by clicking the
"Place Order" button , this looks good. We have the funky message, go over to the Firebase console. This is
console.Firebase.google.com. My Pizza Planet V3
into the database. We now have oldest collection, we'd an order ID,
this looks fine. We've got one 9 inch
Margherita and two 12 inch. Let's try one more 12
inch Pepperoni and Margherita, place your order. Good, everything is working
correctly and next, we're going to fetch
these orders from the database and display
them inside the admin.
37. Fetching Orders: Now we can successfully place orders and add these
to our database. We now have the task of
retrieving these orders from the database so we can use
them inside of our project, and in particular
the admin section. Earlier, we created a current orders section
down at the bottom. We're going to retrieve
these from the database, loop over these and place
them inside of this table. To keep things organized, create a new composable inside
the composables folder. This one is going to
be called useOrders. As ever, we need to export
a default function, useOrders and place the
code inside of there. To begin, we need to have a constant to store all
of our orders inside. We'll call this allOrders. We'll also make use
of ref which is going to surround our array. We'll initialize
this as an empty array and then we need to import this ref from
the view package. We also need a function
which is going to retrieve these items from the database and push
these to our allOrders ref. We'll create a new async
function, All getOrders. We could do something similar which we've used in the past. If we take a look, our
composable, which was usePizzas. Inside of here, what we've
done in the past is we've retrieved our documents
with getDocs. We have this function
called getPizzas. This one is a little bit
more simpler because all we do must call
our getDocs method, and pass in a reference
to all pizzas collection. If we wanted to, we could
do exactly the same by passing a reference
to our orders. But for this use case, remember that in
the previous video, we also pushed a new
order to the database, which included a property
called createdAt. When we're pulled
in the pizzas from allPizzas collection
that weren't in any particular order. This is where this
works a little bit different to allPizzas. We want to make sure
that we're pulling it the orders in a particular order based off the data
in which they were created. Back to useOrders. To do this, we need to make use of something called query. We need to import
this. Import query. This is from the firebase
slash firestore package. From our firebase.js
file will also need to import this reference
to allOrders. Now back to our
getOrders function, we're going to make use of this query which
we just imported. Query. What we need to do here is we need to pass
it in three things. The first one is a reference to our collection, which
is dbOrdersRef. Then we can also pass in
additional search parameters. In our case it's
going to be orderBy. OrderBy. This is also a firebase
method which is going to order the contents that
we're pulling from our database by a
particular field. If all we can use this, we
also need to import this. Then we pass inside
the name of the field, which you want to order this by. In our case, as we can see
when we created a new order, this property is
called createdAt. Let's grab this, paste this in, and then store this reference inside of a constant
called queryData. From here on out,
what we do is pretty similar to when we
retrieved the pizzas. We need to make use of getDocs. We need to then loop
over these docs, and then push these
to our constants. With this in mind, we'll
copy all of this section. Just above the catch block
will copy the brackets. These are the brackets
for each loop up to our the documents. Copy this, paste this just
under our query data. Again, we can make use
of lots of this data. We can keep this constant name. We can search for our getDocs. All we need to do
instead of searching directly for allPizzas
or allOrdersRef, we're going to pass in
our filtered queryData, then we'll loop over each one
of these documents instead of creating a pizza we'll
structure a new order. The id is accessible in the exact same way
since any one of these documents comes back from firebase with the
same structure. We can also merge in
the document data, which is all the
fields from allOrder, such as allPizzas, and our createdAt dates. The final difference
is this line here, instead of pushing to allPizzas, we pushed to allOrders. We'll push allOrder
instead of the pizza. We'll give this a test
before we place this inside of our components, just below our closing
brackets for loop, place in a console log
or allOrders, no value. But this console log
to actually run, we need to import
this composable into the required components. This will be the
allOrders dot view, which is inside of components. Then inside the admin
folder, open allOrders. We don't currently
have a script. Create a script, setup, import our composable,
which is useOrders. Then using the structuring
we can import our allOrders, which I'm not sure we've
actually returned, so we'll take a look at
that in just a second. Call our useOrders
function. Just go back. We need to return all
orders to make use of this button inside our function. Return allOrders. They should now run
our console log. Enter the admin section. Open up the console. We currently don't see anything inside of here since we have not called our
getOrders function. What we'll do, we'll just blow our function is we'll
also import or mount it. We'll run our function
called getOrders as soon as this
component is mounted. This also needs to be
imported at the top. We also need to
import getDocs from Firestore. Let's give
this another try. Open up the console log
into the target where we can see we've got an array
with two separate orders. These are structured
exactly like we created. Inside of this order
here we have the id. Then we merged in the rest
of the document data, which was the pizzas
object and also createdAt. We're almost done
now with this video, the next thing we're going to do just to round things off, is to wrap our code inside
of a try and a catch block. Jump into the
getOrders function. Grab the full contents from the console log of
total query data. Place in, try and also
catch. Catch takes in error. Then pasting the contents
inside the try section. We know this is working
so we can also remove this console log. We'll
leave this one there. Coming up in the next video, we'll move on with
our orders and loop over these and place these
inside of the admin view.
38. Looping Over Orders: Now we know we can successfully retrieve the orders
from Firebase and store them inside of
our allOrders variable. We've imported these inside
of the orders.view file, and we can make use
of these to loop over and display this
inside of the table. This is the table
that you see inside the admin view down
at the bottom. Just like we did with
the pizzas above, we can loop over all of this data and display
the correct values. The first thing inside
of the current orders, we need to change this
hard-coded value of five. We can get the real value
with allOrders.length. We currently know
we've got two orders, so this looks fine. Then have the table headings. We don't need to loop
this section because we don't want duplicated headings, but what we do need to do
is to create a loop for both of these following
two additional rows. The first row with
the order number, and the second row with
the pizza details. To do this, we'll create an additional wrapper
called template. Cut out both of
these table rows. We can pass in an
element called template, paste these back in,
and we can now make use of this template
to add loop two. Template will add a
wrapper to this section, but it will not add
any additional HTML, so it's effectively
an invisible wrapper. Place in a loop with v-for, order in allOrders.
We need a key. We know we've got
a unique key for each order stored inside of ID. This is order.id. We can also make use of this order.id instead of
this order number. Then we can go down to
our pizza information which is stored inside this row. Remember though
for each order we don't just have
one single pizza, so therefore we need to
create an additional loop to loop over each one of the
pizzas inside of our order. For example, we're
currently looping over this first order here. We then need to loop over
orders.pizzas to access all the values inside and
display these as table data. v-for, and we'll say
order item in order. Order is the full orderItem, which you have from
the first loop, but we want to
access the pizzas. We can see if we go over to the console and jump
into the pizzas, we don't have the pizza id
stored for each one of these. But each pizza name and
size is also unique, so we can create
a key from this. We'll say the orderItem.name. We'll see oderItem.size. We can access the orderItem on the pizza properties
such as name, size, quantity and price. The first one is the name, the second one is the size, so orderItem.size, quantity. It's up to you how you
want to handle this price to display inside
the admin table. If you wanted to, you
could first of all try to output the
orderItem.price. If we go into the admin and then refresh, we can
see our structure. We've got the order number,
the pizza name, the size, the quantity, and
the price will be displayed as a single
price of one pizza. If you wanted to,
just like we've described here where we
said this is the total. I'm going to multiply the
price by the quantity. Let's multiply with the star
by the orderItem.quantity. This is now the total for
each one of these lines. Next, what we're going to do is to come back to these orders, and we'll see how
we can remove items from our database
using this red cross.
39. Deleting Orders: Next we'll be focusing on
how to delete the orders. If we go over to the
usePizzas composable file, this uses the same process as we used before when we
deleted the pizzas. We had this function
called delete pizza, which took an id, we cleared out any
existing error messages, we then stored a
document reference by accessing our collection,
and also the id. We called a method called deleteDoc which took in the
pizza we just referenced, and then finally we
then called getPizzas, which pulled in
all of the rest of the pizzas which were
still available. So to keep things
nice and simple, we'll copy over all of
this function and then go into the use oldest
file and paste it in. Of course, we need
to make a couple of little changes we'll
call this delete order. Again, this will still need
an order id passed to it. We'll also clear any
existing message. This will be const order, this will be the dbOrdersRef, and we'll delete this order. GetOrders and in
the function call this one should match the one which you
have just above here. We can then change the message, there was an error
deleting the order. Then this will also need
some additional import, we need to pull in our doc. We need to pull in deleteDoc, and we'll also need to
create this message too, so let's jump up to our
imports, and from Firestore, we need to reference our doc, and also delete doc. Since we're dealing
with our orders, we already have
the orders impulse just here, so that's
completely fine. So we have the message. We need to then
create this as a ref, const message is equal to a ref, which we'll initially set
to be an empty string. We already have this
imported, so this is fine. We then need to return
some values back from this composable down to
the return section. We'll return back,
I would delete all the method and also the message to display
inside the components. We're then going to go over
to our components into the admin folder and
into the orders, the orders.view, where we can
then create some imports. We're already importing
our use orders file, so we can then destructure, delete order and
also our message. First of all, we'll call delete order if
we take a look at the admin section
and then go down. So we all do remember
that each one of these order numbers
has this little cross, which is a button, and this
is going to call our method, which we can pass
the order id to. This is the button just here. The class of btn remove. We listen now for a click, which we'll call delete order, and delete order is going
to take in the order id, which we're going to
pass to this function. I will need to grab this
from our loop so we have order the id which
you've already used. We can just put this
inside the brackets, and this will be passed
to our function. Let's give this a try.
Into the project. We've only got two orders
currently inside of here. We'll try to remove this one. We see this jumps up to
be three orders now, but we do need to currently refresh to then see any updates. But this is fine for
now, we'll return back to this in the
future and we'll improve on this by adding our real-time functionality
from our database. This will mean as soon as any database changes will happen, our application is then
updated with the new values. Finally, remember we also imported this
message at the top. What we can do is
display this just below our header inside the P element. Not only do we need to output
the message inside of here, but we also need to add
some conditional rendering. We don't want this
message to always be displayed or take up any
space which it currently is. If we take a look and
remove this and save, you can see if we comment
this out and hit "Save", this section will
take up some space regardless of if there
is a message or not. So what we'll do to only show
this if there is a message, placing the v if section to only show if there
is a message in place, and for styling, we'll also
add the class of error.
40. Show & Hide Admin Blocks: Inside the admin view may look okay at the moment
but imagine if our application was a
lot bigger and we had lots more pizzas in the
menu or lots of orders. This page could get really long. To help with this, what
we're now going to do is to show and hide each one
of these sections. It makes the page
a lot smaller and we don't need to read
all this information. For this, what we're
going to do is to create a local variable in each one of these free admin components, which will then be toggled with the text of show and hide. To begin jump into the sidebar. The components, the
admin, and new pizza. Well, we'll create a
new local constant called show newPizza. Set this equal to ref
and set up our ref, which will be equal
to the value of true. We'll display this by default. This will be visible inside the admin and then once
a user clicks on this, this will be toggled
to be false. We'll do this inside
of our templates and in the header section
just below our title, places inside of
the small elements. Inside here we want to show
you the text of show or hide. We can also conditionally render this section inside of
the double curly braces. What we can do is we can say if show newPizza is equal to two, we display the text of hide. If it's not we'll say show. Currently, this
is set to true so we'll see the text of
hide up at the top. We can add the
class of toggleBtn. Listen out for a
click when I click. All we want to do inside
of here is to search your newPizza be equal to the opposite of what
the current value is. Let's try this out. Click on hide. This will
now toggle and then we can use our show newPizza variable to show and hide
this full section. The section which we want to
show and hide is the form. We can still keep
the head in place, which has the button
and also the title. Inside the form, add
v-show which will only show these contents if the value of show
newPizza is equal to two. It's not currently
toggling back. Let's try and refresh. That seems to work fine. I'm not quite sure
what happened there. We can now replicate
this section in the orders and also the menu. Let's jump into the orders. Import our ref vue. Create a local variable. This one can be show orders. The initial value of true. In fact, what we'll just
do here is we'll copy over the newPizza section we created, so the full small section. Bring this over so
it's consistent. Place this just below
our current orders, Level 3 heading,
this same class. This time, all want to do is to change this to be show orders. Change this in all of
the other sections. We then want to show and hide. Our table would be show equal to show orders.
Let's give this a go. It can hide. This has gone. Then show reveals this section. Finally, the menu section, which is in the pizzas or vue. We also need to import ref vue. Our local variable
called showMenu, the initial value of true, and then just like before, below our Level 3 heading, we need to once again
copy this small section, paste this in, and then replace all of our
variables with showMenu. Finally, add v-show to our
table. Let's try this out. The menu, hide, and show. Just as a quick finishing touch. If we hover over this button, we can toggle this
to be a pointer. We have a class of toggleBtn for each one of these sections. Since we use this in
multiple components, go into our assets and the
main.css class of toggleBtn. We will set the cursor
to be a pointer. There we go. This now works for all
three of our sections.
41. The Sign Up Function: This project is going to use
Firebase authentication to manage user accounts and allow them to sign
up and sign in. If we go into the
Firebase console and into the Project
overview section, you should see just below we
have our authentication and our Firestore Database
shortcut setup. This is because in
the early videos we already enabled these
in our project. But if you don't currently
have authentication setup, all you need to do
is to go down to the all products section and add it with this
authentication link. Since mine is already enabled, I'm going to go back
to the project to set this up. Let's
keep things organized. Let's create a new
composable inside of the composables
folder, a new file. This one's going
to be useAuth.js. Then, as with all of
the other composables, we'll export a default function. This one is useAuth. Then we'll add a
return statement down at the bottom
before we forget. Since we don't currently have
any users in our project, it would make sense the first function we add
is to create a new user. For this, we need to
import some functions from the Firestore Firebase
authentication package. Above our function, we'll
do these imports now. What we need to import is
something called getAuth. Also to create a new user, we need to import a
method called create user with email and password. This is all camel case. After the first word
makes sure that every following word begins
with a capital letter. We're going to import
this from Firebase/auth. Next we'll make use of
this getAuth import, which is going to initialize
Firebase authentication. Then we'll start to reference
this inside of a variable. Jump into our function. Call this auth, which
is equal to getAuth. Since this is our
method, we need to call this with the brackets, the next auth function, which is going to
create a new user. This is going to be
asynchronous called sign-up. When we sign up,
since we are creating a user with email and password, we also need passes to this
function when we call it. We'll take a look at
how to do this soon inside the function
body or error handling. Create a try and a catch
area, passing the error. Then inside of the try section is where we're going to call the create user with email
and password function, which we just imported. Let's await the return value. Create user with
email and password. This is going to take in
three separate things. The first one is a reference
to the current of instance, which is the one which
we set up at the top. It also needs to take in the
email and also the password. Here we are. Also to
store any error messages, we can create a new
reactive variable. For this, we first need
to import ref from view and create our constant just below off inside the function called
errorMessage. Then set up our ref, which
is going to be initially an empty string. There we are. If this section is
now successful, if the try area is
all working fine, we can then clear out
any existing messages. We'll reset the
errorMessage.value to be back to an empty string. Then inside of the
error section, we can also update this
error message too. If we are doing something
like signing up a user, we don't just want to send
back a generic error message, such as 'there was an error, there was a problem signing
in' or anything like that. It would make sense to
be a little bit more descriptive about
what the error is. They might not type a password
which is long enough, it may not have the correct
characters in place, the email may be already in use. For this, we need to
access our error, which is passed to
the catch area. We have a property called code. This code is a string of text which is equal
to an error message, and two common ones which
Firebase provides is a string called auth/email
already in use. It looks like this. It's
auth/email-already-in-use, with dashes between each
one of these words. Also, another useful one
is auth/weak-password. These will be useful messages
to pass back to the user. But first, we need to make
sure that we can detect which one of these is being generated inside of our error. This we can place in
an if-else statements, we can place in a switch
statement, whatever you prefer. I'm going to go for the switch. We can add to this later if
we want to access the error, which is passed to
our catch object, then we can access the code. Then we can begin to build
our individual cases. The first one, we'll
check for this string just here. Paste this in. If this is true, if
this is a match, we want to set the
errorMessage.value. To be a little bit
more descriptive, we'll say something
like a user with that email already
exists, please login. If this is true, we can then break out of this
switch statements. We don't need to go any
further down another code. The second case is equal
to our second string, which we had down the bottom
here. Let's move this. Paste this in as our case. If user enters a weak password, this will be an
errorMessage.value. We'll update this
variable to say the password should be at
least six characters long. We then break out of this of
this if this one is true. Finally, with the
switch statement, we should also add a default
message down at the bottom. This is for if any of the
above cases are not matched, then fall back to
the default section, which will again update
the errorMessage.value. This one can be a little
bit more generic. We'll say, sorry, there
was an unexpected error. This is all we need in our
sign-up function for now. The last thing to
do is to return our sign-up function and
also our error message. Both of these are
now going to be available to call
from our components. We'll do this next when we
create the sign-in form.
42. Creating The Sign In Form: Great with our sign-up
functionality now working, we also need a form to capture
the user's information. For this, this form is
going to be dual-purpose. It will allow the
user to both sign in and also sign up
to create a new user. For this let's jump over to our sidebar and we can
create our new components. Jump into the components, and this one is SignIn.vue. First, we'll create
the script section which will make use of setup. At the top, we'll import
our use of composable, which we created in
the previous video and set this equal to @, which is the root of our
source, composable UseAuth. Grab our variables
which we exported on the first one is sign up. We also need our error message. Grab these from the
above, UseAuth function. This just needs to be
from, there we go. Also a form data
variable to store the email and password
which the user enters. We'll link this to our
form using v-bind. This one is called
the user data. We'll also need to import the
ref from the vue package. Set this up as an object, which is simply going to contain the email and also the password. By default, both of
these will be empty since the user has not
entered any values. Then import our ref from vue. Down to the template
area to create the form. Since this one is going
to be a pop-up modal so we can show and
hide using CSS, we do need to create a couple
of wrappers to enable this. The first one will be a div
with the class of modal, which is the main wrapper. Then also nested
inside will create a second div with the
class of modal_content. This modal will be toggled
inside the header. What we'll do soon
is we'll create inside the header a
link about the top, which will then pop up our modal of all
of these contents. Then inside the modal,
we also want to create a little
cross in the corner, which will then close
down this model. The close will be a span and the class for our
styling of close_modal. The close will be a HTML entity, which is the
ampersand, the hash. Then 10060 and a semi-colon. A p element below, this one is going to
be for the text so please login to continue. Add a class for our
styling of modal_text. Then below this
we'll create a form for the user to sign-in. We don't need an action, we'll handle this with future yes. All we need inside of here
is an input for our email, an input for the password. Also we're going to
create two buttons. Let's begin with
our email section, which will be a div as a
wrapper class of form_group. The form label of email address. The input for this one,
since this is an email, we'll give this
the type of email, the ID of email, the placeholder text
of enter email. Then we can also
use v model to bind the value of this input to
our user data just above. We need to access
UserData.email. There we are. This inputs
also has the ID of email. We can link this to our label and then
copy our div section. This one is for the password.
Paste it in just below. The label for password
and for the input, the type for this one
is also password, the ID is the same. The placeholder of
enter password. This needs to bind it
to UserData.password. Then our two buttons down the
bottom, just below our div. The first one is sign-in. Place in a button with
the type of button, the text of sign-in. Duplicate this or
copy and paste. Now what we need to do is to change this text to be sign up. Later this will give
the user the option to sign-in or sign-up using
the same form modal. There we are. We've created a new sign-in modal components. To see this, we
need to import it exactly where we want
it in our project. As mentioned before,
this will be inside the header so we can see the
text up at the very top. Let's go into the header
up to the script section. In fact, we don't have a script, so let's create this now. Script setup. We'll import our components,
which is sign-in. The file path, since
we're inside of the components is
simply./SignIn.vue. Then place in the components at the top of the header section. You may be wondering
why we add in a sign-in form into
this header area, but this will become
more clear very soon. But placing this inside
of the header for now, we'll keep this
group together with the sign-in and the
sign-up buttons, which will display at the
top of the header very soon. We'll click on a button, this will then pop up the form and then we'll be able to
close it down with this cross at the top. Also it looks a little
bit messy at the moment, but we'll fix this later
on with our styling. For now, let's concentrate on the form and
signing up a user. Back over to the sign-in components which
we just created. Since we've created
the sign-up function inside of our composable, we'll work with this sign-up
button down at the bottom, and we'll link this
to the function with a click listener. We can prevent the
default behavior, which is to refresh the page, call our sign-up function, which is going to
take in the email and the password as we set up
inside of our composable. Both of these are available
inside of the script. We have the UserData.email
and password. Let's pass in both
of these values. First is the UserData.email,
then the UserData.password. It should be all we need
for the sign-up function. Remember, we also imported
the error message. Let's display this just below our p elements with the text
of please login to continue. Place it inside a span with
the class of error_message. Inside the double curly braces
pass in our error message. We did quite a bit there.
Hopefully this all works. Let's jump over to the
project, gives us a save. We'll create a new
user to continue. It's a little bit
difficult to see, but we'll fix this in
the CSS very soon. Place in an email address, and also a password. Just see this, if I hover over, click on the "Sign Up" button. At the moment, we don't
see any feedback, so we don't currently have an error message
which is a good sign. We will also close
this form down automatically after
signing in and signing up. But for now jump back over to the console into the
authentication section, but we should see the user, which would just signed up.
43. Modal Styling: [BACKGROUND] We currently
have this not so great looking modal which has
the login components. Over in the sign-in.view, we can begin to add some styling by targeting all
of these classes. The main div which surrounds all the modal has
this class of modal. We then have the modal content
which is contained inside. These can now be used
to style our modal and also make it appear over
the rest of the content. We jump down to the very bottom, create the style section, which is going to be
scoped to this component. As we have seen, the main
wrapper which surrounds all of this div has
the class of modal. What we're going to do here
is we'll target this modal, which is the full wrapper. We'll stretch this wrapper to be the full width and the
full height of the page. We can then set a
darker background color so we can't see the
content behind as much. Then on the next div, which is nested inside,
which is the modal content. This will be a
little bit smaller. It will also have a white
background color to make this stand out over all of
the content behind. First, the modal section. The modal will have
a fixed position and it will all stand out
above all the rest of the content by [inaudible] index to be a high
number, such as 999. This will make sure that
this full background appears over all of the
existing content. To make this modal stand out, we also need to set
the width to be 100vw. The full width of the view-port. The height, 100vh, which is 100 percent of
the view-port height. The background, this
would be an RGBA color. What we'll do for
the red, green, and blue is to set this as zero, which is the dark black color. We'll set the opacity to be 0.9. It's slightly transparent. This is the effect
we're going for. It's the darker background color which is slightly transparent. We can also position this to be up in the top left corner, so we don't have this
gap up at the top. We can also make use
of the flexbox to center all of the content. First of all, the top is zero. We'll do the same for the left. Make sure we don't have
any gaps around the edge. The display type of flex. Then we'll make sure
all the content is aligned vertically and horizontally by setting the justify-content
property to be center. Also the same for align items. It should now be all we need. We have this modal
now in the center. All of its contents in
the center is going to place on a white background
so it's more visible. This has a class if we go
back up of modal content. Select this, the background can be any color
which you want to. I'm going to use white smoke. It also needs a little
bit of spacing, padding. We'll set the width of it, we'll set the border-radius, and also will center all of the contents using the flexbox. We'll set the width
property to be 80vw. One remember of padding on all sides to give this
some spacing inside , a small border-radius. Let's say three pixels. Also, we'll set the
display type to be flex. The reason why we are
going to be doing this is because
we're going to set, if we go back up to the top, we're going to use this
flexbox because of particularly this
closed modal button. We want this to be
over in the right and also the text which
is just below. But we'll come back to
this in just a moment. But now we need the flex
direction to be in a column, which will reset the
contents to be vertical. The color and RGB
value of 76, 76, 76. You can also see in this text we've inherited
this tech shadow. We can override this way to the text-shadow property
by setting this to be non. Let's now move down
to our buttons. We can select the modal content. Inside of here, we
have our two buttons. We could add an
individual cluster, each one of these if you
wanted to, or instead, we could use CSS to select the modal content and the
button which follows, which is the first of type. This is going to select the very first button inside
of our modal content, which is our sign-in. We can use this two spaces out with some margin
on the right. We'll also make this look
a little bit different by setting the background
color to be an RGB value of 163, 204, 163. As you can see,
this has selected the very first occurrence
of this button. We set the background
color and also added some space in it
to the right-hand side. Next, for the hover
state will add a small transform to this. Again, we'll grab
the modal content, select all the buttons inside. Only apply this when the
mouse is hovered over. Transform. Make this
slightly bigger. We can make use of scale. The scale value of one is
exactly what it currently is. To make it slightly bigger, we can set this to 1.03, giving us a nice transform on the hover over each
of these buttons. Next, let's move up
to the top where we have this close
modal section. This is the span which
we have just here. Remember we set
the modal content to make use of the flexbox. Therefore, since closed modal is a child element of this, we can then use certain
flex properties. To do this, jump
down to the bottom, grab all close modal. The flex property of align-self and the
value of flex-end. What this will do,
this will only apply to this current element. Over on the left, this
will be the flex start. Over on the right
is the flex end. We've just been a button and
we can set the cursor to be a pointer. This is now done. What's left? We have the text. Just here, we can place
this into the center. This one, if we take a look, has the class of modal
underscore text, grab this, set align-self
to be equal to the center. It now keeps our texts in
the very center of the page. Let's just shrink
this down. The width for this section is fine
for the mobile view. But if we stretch this out to the larger view
which we've seen, we probably want this modal
to be a little bit narrower. Let's jump into a media query, and we can do this @media and will only apply this when the screen size is
900 pixels or wider. Select the modal content. I will go to, set
this to be 40vw. Let's try this out
down below 900 pixels. Then over 900 pixels
would reduce the size. Almost there now, the last thing which
we need to look after is this span with the
class of error message. For this, I'm just
simply going to add some sample text.
We can see this. Set the color to be red
and also some space into outside the media query. Grab our error_message. The color of 255, 104, 104. This is the red color
and it also just needs a little bit of
spacing on the left. Let's add some margin
on the left only. This looks pretty
good. Now we can just go back up to the error message. I remove the sample content
from here. Here we are. This is now our styling
finished for the modal. Coming up, we'll take
a look at how we can close this modal when
we click on this X.
44. Modal Toggle: This modal now
looks a lot better. We'll also need a way
way open and close it. For this, let's jump
over to our composable, which is useAuth.js. Then inside of here, we'll
create a new variable which is going to hold the
current open state. This will be a Boolean value, which we'll set up to be
an initial value of false. This will be called
a signInModalOpen, will be wrapped inside a ref
and the initial value of false and then below create a new function which is going to toggle this to be true or false. Function toggleModal. All we need to do inside of here is to grab our variable name, which is signInModalOpen, set the value to be equal
to the opposite with the exclamation mark, so
signInModalOpen.value. If this is false,
this will then return this to be true and
if this is true, this will then set
this to be the opposite, which is false. Return both of these from this file so the
signInModalOpen, toggleModal and then jump into a component
which is sign in. I need to import
both of these from useAuth toggleModal
and signInModalOpen. The signInModalOpen
constant will control if the user
can see this form, so we need to add this
to our wrapper inside this div with the class of
modal at v if statements, which is equal to this value. Now, if we save this, because the initial value is false,
this is now removed. Then back into our template, where we have this span, which is to close the modal. We can listen out
for a click, which will trigger our function
called toggleModal. As we know what this
signInModalOpen constant is set to the initial value of false so to actually
see this modal, we need to create a button, which is going to
change this to be true. The problem we
currently have though, is all of this content inside the modal is hidden by default. Therefore, to actually
see this button, we need to create a
button outside of this main div with
the text of sign-in. This also needs to
listen out for a click, which is going to toggleModal. The styling for the
class of sign_in_btn. Give this a save and we can now try this out inside the browser. There's our Sign In
button. Click on this. This will now toggle this to be true therefore
showing the modal. Click on the cross, which will
then hide this from view. Just to finish things off,
let's move this button over to the top right-hand corner and we can also
remove this border. That's the bottom inside
of our style section. Let's grab our Sign In button, so sign_in_btn, all
separated with underscores. Remove the border with
the value of none. Then using the flexbox, we can make use of a line self. We set this to be flex-end. Now push this over to
the right-hand side. To make this more visible,
we can override the color by inheriting this lighter
color from our parent. Finally, we'll also
access our Sign In button with a hover state, and give this a
small color change. rgb 161,132, 132. Let's see how this
looks. There we are. Now our modal will
toggle open and closed, which is working really good. The next steps will
be to actually log the user in and also
sign them out too.
45. Logging In & Out: Our sign-in form is designed
to be dual-purpose. We've already used it
to sign in new users, but next we'll also
use it to login any existing users too and keep things
nice and organized. We'll place this login function inside of our use of composable. Then into use off,
we have our signUp function so let's also add
to this a sign in function. Before we do this, we can also import a method from Firebase. Before we had to
createUserWithEmailAndPassword, but now what we need is
signInWithEmailAndPassword. Make sure this is all
coma case so each word begins with a capital
letter after the first one. This will be responsible for signing in any existing users. Let's create a
function which we're going to call this inside of. Once the user hits
the "Sign-In" button inside of this model. A lot of the code
will be similar to the sign-up function we
have the try-catch block, we'll then call our
particular Firebase method, and we also need to handle
any error messages too. What we'll do is we'll copy
all of this sign-up section, I'm going to use this
as a starting point. Paste this just below. The function, we'll
call it login. Instead of create user we'll
change this to be sign in. Sign-in with email and password, we'll then clear any error
message, which is fine. After signing in,
we also want to close down this model,
so we'll click "Open". We'll enter our details. If the sign-in process is fine, we'll click on this and then
this will be closed down. We can do this by
setting the sign-in modal open to be equal to false. Sign-in modal open is something which we already
have set up at the very top and this controls
the visibility of the modal. Back to our function and
down into the catch area. We don't need the
first case of email already in use because
if we sign in, in the user, the e-mail
should already be registered. What we'll do is we'll grab the message of wrong password. As with the sign-in function, these are all messages which are returned
back from Firebase. For this one, we'll say
incorrect password. Number 2 is user not found. We'll say no user
found with that email. I'm going to keep
the default message if it is any other errors. Just before we call
the try section, we'll place in
conditional statements. In fact, we'll place
in two and we'll check if there is no
email or password. If either one of
these is missing, we'll return out
of this function and send a message
back to the user. If there's no email,
we'll return back a statement which says
error message. That value. Is it equal to please enter a valid email? Duplicate this. The second one will test if
the password is not present. Please enter a valid password. This will just stop an
unnecessary method called Firebase if we don't have all
information which we need. Now we'll have the function
to create new user, to also log in an existing user. Next, we're going to focus
on signing out a user. For this, Firebase provides
a method called sign-out. Import this. Then we'll
also create a new function, which is going to call this. Just a button above
our return statements. Create a function called logout. We'll call our sign-up method, which also takes in
the off object so we know which user to sign out. In fact, we'll also wrap this in a try-and-catch area so we
have some error handling. Places in the try section
will catch any errors, and then send an
error message to the user with the value
equal to error.message. He will access the error object which is passed to
the catch area, and then access the
message property. Both of these two new functions
also need to be returned. The first one of
login, then log out. Both of these need
to be accessed inside of the
sign-in components. Add into that,
components, signin.vue. Import these at the top, so login and logout. The login function, we need to place this down
inside the form, we'll link this to
the login button. We have the sign-up
already being called. We have the sign-in just above. We listen off the
click with this one, prevents any default behavior,
and we'll call login. And then, just like with
the sign-up function, we also need to
pass the e-mail and the password which
we have stored in the user data object up at the very top,
which is just here. And this is bound to
our to form inputs. So the email, the password,
we'll click "Sign-in". Our function will then
be called pass in the email and also the password. But what about the
log-out function? Where are we going
to call this from? Well, currently we don't have
any logout button inside the modal or also
inside the header. But we do have the sign-in
button in the corner. It will be nice to toggle
the text here and only say sign-in when the user
is actually signed out. And also the opposite, when we need to use
it to sign out. So let's do this up at
the top of the template, back into the
sign-in components, and up to the top
of the template, we've got the Sign In button. Let's duplicate this. I click, say log out, change the text
to be signed out. We'll come back to
these buttons very soon and only show one
of them at a time. But now we can see our sign-in and sign-out buttons at the top. But just before we go
ahead and test this, how can we tell if we even
logged in in the first place? Well, one way is to get
the user object returned back to us after we call
the login function. If we go back over to
our use of composable, the way we can access
this inside of our login function is by storing the return value from the sign-in with
email and password method. We saw this is inside of
a constant called user, and then just below,
we can give this a test with a console log. Place in the user, onto the browser and open up
the developer tools. Jump into the console, sign-in, enter the e-mail
which you signed up with. This time we'll hit
Sign-in. We see an error. Sorry, that was an
unexpected error, but we do seem to be getting back the user credentials.
Let's take a look. We have the user, we have the email,
the access token. This all seems to be fine, but we're still getting an
error message inside of here. So let's take a look. This is the message inside
the switch statements. Let's do a little bit
of debugging inside of the default section
place in a console log. And here we'll output
the full error object, and we'll see if we can get
a better idea of what is going on. Sign in again. Sign in. So we get the error, we have the user credentials
and there's our error. Assignments are
constant inside the use of so sign in modal open. That's the problem.
This needs to be.value. Let's try this once more. We do the refresh to make sure that any
errors are cleared. Sign-in, great, the
message has all cleared. We still have our user
object and the modal is closed down since we've set
this to be equal to false. Let's remove the console logs. We don't need either of these. Returning back this user
object works completely fine and this is one way of detecting if the user is logged in or not. We can also access
the user properties such as the email and ID. But this is just one
way of doing things. Another possibly
better way of doing things is to make use of a
Firebase-provided method, which we're going to take a
look at in the next video, and this will listen
out for any changes to our signed-in user.
46. Detecting Auth Changes: As mentioned in the
previous video, when we call this
method which is sign-in with email and password, this will return back a value. If successful this value will be the contents of the user object, which contains all the
information we need, such as the user ID and
also the user email. This is fine, but Firebase
also provides an observer, which is a recommended way
to not only get the user, but to also notify
us if there is any changes such as
a user signing out. This observer is going to be imported into our use of file so jump up to the top and
inside of the off import, we'll also import
on/off state changed. To call this we'll jump down
to the bottom of our file, just above the return statement and we'll call this
at the bottom. In this painting method, we also need to place
in the brackets. We also need to pass
in two parameters. First is the occurrence
of instance, we have access to this. We'd have our off constants
separated by comma. The next parameter is going
to be a function to call if a change in the users of
State has been detected i.e., this function will be
called if the user logs in or logs out. Passing a function as the second parameter and its function is also going to take in the
current user. Open this up. Now we'll have access to
the current logged in user, we want to know our place in
a conditional statements. We can check if the user exists. If it does, we'll
then set the user. If it doesn't exist, we'll then set the
user to be equal to null and where we're
going to set this, well, let's create a ref up
at the very top of our file. Just below the
rest we say const. User data is equal to a ref. By default, we'll set
this to be equal to null. Back down to our observer, we can place in a
conditional statements and check if the user exists. This is a user which will
be passed to our function. If they are logged in, we'll set the user data value to
be equal to this user. Else the user is not
logged in so therefore, we can return the user
data to be equal to null. Let's then return this user data back from our composable. This is going to be used
in various components. One of them can be the sign-in
components to make sure we only show the relevant
button at the top. Jump into sign-in,
import this at the top and then go down to our first two
buttons we see here. We only want to show
the sign-in button if there is no user data presence. Passing VF will say if
there's no user data. If we don't have a user we'll offer the chance to sign in, if not passing VLs
where the user is currently logged
in and they have the option to sign out. Now this will give us a
chance to properly test this. But just before we actually
go ahead and test this, we now have a clash without
two variable names. I'm just going to change
this one to be form data. Let's copy this and we use
this down inside of our form. Form data dot email, the same for the password and also changes in our
login and sign up functions. I think this is everywhere. Let's just check
such a user data and we only have this
in our two locations. Good, we can now go
ahead and test this by putting the
user at the top of the template inside the curly
braces we say user data, the e-mail but remember
sometimes we won't have access to this email because the user data will
be equal to null. Facing the question
mark to make this fail silently if we
have any errors. If you are unfamiliar
with this question mark, this is JavaScript
optional chaining. This will stop an error appearing
if no email is present, which will be the case if
the user is logged out. Let's say this go
over to the browser. We see we're
currently logged in, the e-mail is about the
very top click Sign out. This n is removed so let's
try signing in one more time. There's our email now returned. So to remove this output
from the template and everything now appears
to be working correctly. This is now the end of the
authentication section. Congratulations on
reaching this stage. We're not done yet
though we have plenty more features to
add to our project, which will add to in
the upcoming sections.
47. Styling The About View: Coming up we have some finishing
touches to our project. We're going to begin
with some CSS styling for this about view. At the minute, it doesn't look too
great below the header. Let's go over to this section, which is in the views, about view, select
the above view, and then we can create a
style section at the bottom. Let's take a look at
what classes we have. That's all we have this
wrapper which wraps all of our content with
the class of about. Then have the intro section, and this section includes
our title and our image. It's basically everything
above this links area. Let's start with
this class of about. We'll start with setting the
display type to be flex, and we'll make sure that this
is in a vertical direction by setting the flex
direction to be in a column. Using the flexbox and this
vertical direction will allow us then to align the
items into the center. Align items into the center, which will then
push the contents into the middle of the page. This is the full page wrapper. Next we have the intro section. The intro section, as
we've just discovered, is everything down
to this image, which is image and the
two lines of text above, so we'll also set
the flex direction to be column on this one. The display, or flex
direction of column. Currently, you won't
see any difference if we go over to the browser. This is because we're
going to make use of a media query very soon, and make this appear in a
row on the larger screen. Currently, this will apply
to the small screen, and then on the
larger screen view, we'll set this up to be
a flex direction of row. Places inside of the
art media rule where we'll target the minimum
width to be 900 pixels, and then we can override our intro section by setting the flex
direction to be a row. These appear in a row.
Then below 900 pixels, this will revert back to
the column direction. Let's now jump back
into the intro section. If we scroll up to the
full section inside here, we have this div
with the class of info text wrapper
alongside the image. Effectively, we just have two sections we need to work with. We can again make use of
the flexbox to set both the left and the right right
to be the same size. We also need to add some
spacing and alignment, and we'll do this just
outside of our media query. First, the image was info underscore image will also grab our info text wrapper, info IMG, and also
info text wrapper. Since the parents of intro
has the display type of flex, the child elements
can make use of flex, and we'll set both
of them to try and take up the exact same value. Some padding to give
both of these at some spacing of one RAM. Align the text with
text-align into the center, and then to set the
vertical alignment. We can also make
use of align self. Align self into the center. Again, this works
because the parents of intro has these
display type of flex, and align self will
work because this is a child item of the flexbox. This takes care of this section. Let's scroll back up. The next one is the
more info wrapper. This is the section for all of our links and this
title just here. Let's target this section
again outside the media query. More info wrapper in display. Again, we'll make
use of the flexbox. We'll set the flex
direction to be in a column text line
into the center. We can make this stand out by adding some background color. Make this a little
bit different from the current white color. Let's go for double
F, double E, D2. Currently, this background will only surround the content, so to make this the full
width for the page, we can change the
width to be 100 VW. There we go. To give
this some spacing, just add a little bit
of padding of one RAM. We can also change
the background of this section too much, so let's copy this. If we go back over
to the source, the assets, the main.css. In fact, we can just
remove the background for the info block and this will now match the rest of the content. Let's try over sections
and also the homepage, and make sure you
will have no issues. Infact here, we do need
to add the background. Paste this in. There we go. This now works for
both of our views.
48. Completing The Admin View: Let's continue with
our finishing touches inside of the admin section. Inside here, we're
going to make use of the flex-box and place in new pizza on the menu components side-by-side on the larger view. Let's go into the admin view, which is in the views folder. At the moment we don't
have much content, we just display the level three heading and all
three components, so that's the section
of our new pizza and also the pizzas component. Cut this out, wrap
these inside of a div, paste it back in, and give
this div the class of row. At the bottom, create
the style section. We'll also scope this to our
components. Select the row. In the initial section here, we'll set the display
type to be flex, a new flex-direction
to be column. This will work on the
small screen view. I'd shrink this down
and we can see these in the column direction. Then set up a media query with the art media rule
where we'll apply all of the styles inside
here over 900 pixels. Select our row, or you can override the
flex-direction to be a row. The smaller screens
will have a column. Stretch is to be
over 900 pixels. These are now side-by-side. We also need both
of these components to be a little bit wider, so they span the full
size of the page. We can go into the new pizza and also pizzas inside the
components under Admin. This one has the class
of admin section. If we go to pizzas, we can see this one does too. Let's go into the assets
folder, into main.css. Do a search and we already have the admin section
reference just here. Set the flex value
to be equal to one. All of these sections
will try to take up the same available space. With this being an admin area, we also want to introduce
some measures to make sure only the correct
people can view this page. For now, we'll make
sure they are logged in by first importing the user. Back to the admin
view, into the script. We need to import our use of composable where
we can extract and store inside of a
constant our user data. Remember, the user data
is either equal to our object which contains all the user information
or it's equal to null. We use Auth to
call our function. Therefore, user data
can be used with some conditional rendering to show and hide the page content. Let's grab everything
inside of our template, place in a div as a wrapper, add the contents back inside, then make use of conditional
rendering with v-if, where we can check if the
user data is present. If we're logged in,
we should be able to see all of the admin section. If not, create a p element, which will be for
the v-else section, with the message of you must
be admin to view this area. There's our message.
Before we log in, we'll just add some styling to this section by adding
a class of warning_msg. We'll style this
in just a second. But before we do that, we can also add a
welcome message if the user is logged in. Just above the level 3 heading, add some text with the
class of user_email. The texts are, welcome. Then inside the
double curly braces, we can access our
user data.email. Remember that the user data is not always going
to be an object, it can sometimes be null. Therefore, we can make use of JavaScript optional
chaining with a question mark to only display
this email if it's set. Finally, the styling
for our user email and the warning message go
outside of the media query, so user_email and
also the warning_msg. Margin of one rem it just pushes this outside of the corner. Let's
try signing in. Everybody, the content
is now visible and also we can see a welcome
message at the top. Also just to push over
the level 3 heading, we can also apply the
same amount of margin. Add the H3. Good, this
looks a lot better now. Let's go over to our sign-up
button and click on this. This now takes
effect and removes our admin section from view.
49. Restricting New Pizzas: Another check we can make is
to see if our user is logged in before they can add a
new pizza to the database. Currently, we don't have
any way of checking if the user is a regular
user or an admin. But we'll come back
to this one later, and why we'll do this
is once again to import our user data into our
new pizza components. Components, Admin, New pizza. Place an import at the
top from our composable. Import, useAuth from
our composable file. Then we can extract
the user data. If we scroll down,
we can see we have this Add functions at our
new pizza to the database. Before we add this, we can check to see if the user is logged in. We'll say if there
is no user data, and since we're
inside the script, we also need to
access the value. If this is false, i.e, this will be equal to null if
the user is not logged in. We can now return
out of this function before adding our
pizza to the database. Under the current circumstances, it is going to be pretty
difficult to test this. Because if we were signed out, we currently can't see the
Add new pizza component to even give this a try. What we can do as a
temporary measure is to disable the conditional
rendering inside the admin. Jump over to the admin view. Then inside the templates we'll comment out the if statements, the closing div, and also we'll need to
do the v-else area. This now gives us access to the admin section when we
are currently not logged in. Let's add, and now every time
we try to add a new pizza, we don't see the message to
say this has been added, and we also don't see this added over in the menu section. Good. With this all working, we can now reinstate the conditional rendering
inside the admin view. We're not logged in, so all
the content should disappear. Next, we'll stick
with this subject of the user object by
adding it to our orders.
50. Adding The User To Orders: The upcoming steps are all
related to all orders. What we're going to do is we'll make sure that the
user object is placed on our order before
pushing to the database. We'll also make sure
that the user is logged in before
creating a new order, and to do this we'll
jump into the use basket composable and
in part of a user. Composables, useBaskets, and this file contains
the addNewOrder function. But before we can actually
test if the user is logged in, we also need to import the user data from
our off composable. So any composable can be imported inside of
another composable, and this makes these
files really flexible. Just like we do in any
of the components, we can import our composable. This is useAuth, and this is./, useAuthf since this is in the same directory and
then into our function, we can extract our user data
from our useAuth function. The ID here is we'll check if
there is a logged in user. If there is, we will
add them to the order, if not, we'll add some buck
a user message to login. And we need to
store this message inside of a variable
or a constant. So const, signInMessage
is equal to a ref with an empty string as the
initial value down to the addNewOrder
function and we can first check if the
user is logged in. Inside the try
section, we'll say if, userData.value, open
up the curly braces. If this is true, we want to run some code inside of here, if it's not, we'll
create an L section. The L section will
update the signInMesage, so this will return
back to the user a message of please sign
in to place an order. Then, in our if section,
we can copy or cut out the order section
all the way down to our basket text without
the original content. Then place this inside
of the if section, so this will only run if
the user is logged in. This is all we need to do
for now inside the function, we can then return
back our signInMesage, which can then be imported
inside of our menu view. The top, we've already
got access to use basket so we can just enter the end our
was sign-in message. Then, render this message
inside of our template, so scroll down to
the order button, which is in the basket section, look for this button with
the text of place button, and then just above this, add a new P element, outputs, our signInMesage. We can now test this when
logged in and logged out. Since we've been
asked to sign in, we are currently not logged in. We can confirm this
with the admin. Let's try to place an
order, click on some items. There we go. Please sign in to
place an order, click on our model
and we can login. Sign in, click "Place order" once more and
this is now all worked. If worked, however,
log out and clear the page and then add
any new pizzas to order. Again, if we tried
to place an order, we'll still see our
message just here. You may have noticed
from before that as soon as we login
via the modal, this message will
still stay intact. Let's just demonstrate this. If we sign in once more, click "Sign in"
keeping this message intact can cause some
confusion to the user. They may think that they
still need to login. All this means some error
with the login process. To clear this what we can do is, view provides a watch method which will trigger
a callback function If there is any change to a reactive state
such as our user. What this means in practice
is if we go back to our use basket composable
up at the top, we can then watch our user data and as soon as any
changes will happen, we can then run a
callback function. The watcher has automatically been imported from
the view package. If yours didn't
happen automatically, be sure to add this in. Then each time a change
happens to other user data, the function will run
where we can clear our signInMessage.value
by setting this equal to an empty string. Let's go back over to
our menu and refresh. We can see this all taken effect at any new pizzas.
Place an order. This is fine because
we're logged in, sign out, place an
order when logged out, and we see our error message. Now, if we try to log in once more, sign in. As soon as this happens, we then remove the
error message so the user is now free
to place their order. Good, this now leaves us with the final task of adding
the user to the order. Let's go back over to the use basket composable and then down to the
addNewOrder function. Just inside the if section, remember that the user data, which we get back from firebase, contains lots of information and many of the properties
which we don't need. For simplicity, I'm
going to create a new user object
and only placing the user's ID and
email, the ID property, we can grab from
userData.value.uid and user email. This one is from the
userData.value.email. Then below, when we
create our order, we can also pass in the
user. Let's now save this. Let's try to place a new order. Place our order, go
into the console, into our Firestore database
and check out the orders. If we scroll down,
we can see that the latest order which we just placed also has our
user object attached.
51. Filters & Global Properties: You notice if we go
into the menu section, begin to add some
pizzas to our basket. If we start to
increase the quantity, lets increase this up to six and see we have some
inconsistent formatting. The first one is down
to one decimal place, the second one is
two decimal places and the order total
is all messed up. If you've used Vue version 2 in the past you may have used
something called a filter. A filter was
available to provide some simple text
formatting and were ideal for creating things
like currency filters. However though in Vue version 3, filters are no longer available. They can be replaced with
something like a method or a computed property
inside of a component. However though, if you
want this to be available globally so we can
reuse it we can also make use of a
filter like setup in Vue 3 by accessing something
called global properties. Global properties is an
object which we can add to, which is available in all of our components for us to reuse. Do be careful though not
to overdo things and make everything
globally accessible. But a currency filter
which we can use multiple components
is a good use case. Let's go over to the
main.js and set this up. So just underneath where
we create our application, way to do this is to call app.config.global.Properties
and then we can give this property name such as dark.Mode and we'll set
this equal to true. Now we can access inside
of any of our templates is darkMode property so it
acts like a variable. Let's go over to the menu view and anywhere inside
this template is fine. We output this inside of
the double curly braces, now we see this up at the top. This is a really good use
case for simple strings or Booleans just
like this and we can access these
in any components. We could also set up as many
of these as we wanted to, we could just duplicate this and we could create
different variables. Or we could also add
multiple properties inside of an object. So let's keep this section
but remove the variable, and what we'll do is we'll
create an object called filters and then inside the curly braces we can add as many filters as we wanted to. For our use case we'll create a function
called formatMoney. FormatMoney needs to take in
the value which you want to format and then we can return the new value
which is formatted. A simple way to do
this is to call value.toFixed and this will fix this to two decimal places. Also as mentioned, since this
is an object we can also add multiple properties
inside of here. But although formatMoney is all we need so let's go back over to our menu view and we can replace the pricing
with our filter. If you wanted to you
could add this to the menu section but currently our numbers are
already added into the database so this should
always display fine. The problem occurs when we add multiple pizzas and start
to multiply numbers. What we can do is go down
to the basket section, we have the order total, we have this price
just here, so instead, what we want to do is to
cut out this multiplication , access our
filters.formatMoney. FormatMoney was a
function which takes in a value to format
so we need to also pass this in which is the multiplication
which we just cut out. These are the line totals and we also need to do the order total. So cut this out then we'll
access filters.forfmatMoney, paste this back in.
Let's try this out. Up the quantity and you
can see when we get past six which was
the problem before, these all correctly formatted
to two decimal places. Since our filter is a
global object we can also use in other components too where we display
money values. Let's jump into the components, into the admin and
into the orders.view. Jump down to our table, just like in the
basket we're also multiplying the price
by the quantity, so we can do the
same here we can say filters.formatMoney. Pass this back in, let's try this out over in
the admin section. This also looks
fine if we look at all the totals
over on the right. This is a really simple
but useful way to repeat simple tasks
throughout our app. Is currency formatting
is a little basic though and
we can improve on this by using the
JavaScript Intl object. This is the JavaScript
internationalization API that provides
language-based number, time, and date formatting
amongst other features. Let's improve on this by going
back over to our main.js. We can replace our current
example just after the return statement
and we'll declare we want a new Intl.NumberFormat. NumberFormat is one of the
many methods which are available on the
Intl objects and this NumberFormat allows
language sensitive number formatting which is
great for currency. Next, within chain onto the end the format method which takes in the value which
you want to format. In our case, we're
still making use of this value which is
passed to the function and as well as this the
number format method will also take in some arguments to specify what locale we are using
and also some options. First, the locale of the end-user which sets the formatting style
of the currency. The reason why we want to do
this is because currency is displayed and
formatted differently in different parts of the world. For example, some countries use decimals or commas
as separators. Let's say 10,000 could
look like this or this and also some countries
use special symbols too. If we currently save
this and go over to the browser, let's refresh. We can see without adding any different options
inside of here by default my currency is to two decimal places and uses
the decimal point in-between. But if we added for example, a German locale which
is a string value of de dash over case DE, as soon as you save
this, this is updated and separated with
a comma instead. My use case I'm going to
change this to be English, dash GB, which should revert this back to
the decimal place. Then separated by a comma we can also add an options object. Inside this object we can
pass in various things such as the style and
the currency options. The style is going to be
equal to the string of currency and then we also need to pass
in the currency which you want to format. This can be any currency
such as Euros, EUR, we can see this just here, also remove this $
symbol in just a moment, it can be $, I think USD. See US just here, but mine is going
to be in pounds. As well as currency this can also be used for
other things too. We can include units of
measure and also percentages, but these options are
great for our use case and also feel free to set them up with your
preferred currency. Just to finish
things off let's go back over to the
orders dot view. We're going to move the
currency symbol which we have hard-coded in place by the menu, we also need to do the
same here and also here. The menu view, remove this. Almost there now we just
have the menu section which is just above. This is the option no price, we can move the currency
symbol put this out, access our filters,
we've got formatMoney , paste this back in. Now our number formatting
has now taken effect.
52. Realtime Pizza Updates: When we update our site, some things updates immediately
and some things do not. For example, if we go over to our Admin and try
to add a new pizza, just all the dummy
information for now. Click "Add". Then this new pizza
is not automatically updated in the menu
until we hit "Refresh". But if we delete a pizza
by clicking on this X, the item is removed
from the Admin. Why do you think this happens? Well, to see this, we
need to take a look at some files in more detail. First, if we jump into
the admin section of the components and then
open up NewPizza.vue, inside of here we
have this function to add a new pizza to our database. All we do here is we provide
some checks and then we add our document
to our database, we update the message. Then if this is
successful, all is good. The pizza is stored
in the database, but our app is not notified from Firebase of any additions. However, though, if we go
over to what we use pizzas composable and take a look
for our getPizzas function. Currently what we need
to do after we've added our pizza is to refresh the page because this will once again call our
getPizzas function, therefore updating the
menu on the right. This one, it seems
to make sense, but remember when
we deleted a pizza, the application was
updated with the changes. The difference between
these two situations is if we go into our usePizzas composable and
take a look for deletePizza, the key difference here is as soon as we've deleted the pizza, we then once again call our
getPizzas function which will then pass the
correct information to our menu components. If we wanted to, when
we added a new pizza, we could also again call
our getPizzas function. But there is also a better
way of doing things and this is to use a real-time
functionality of Firebase. What this means is
as soon as we make any changes to our database, the app is notified
of these changes. For example, when we go over to the Admin and add a new pizza, we click on the Add
button and this is then pushed to our database. As soon as this has
been successful, Firebase will then let
our application know by person the
updated information. Then our menu will
update automatically. We do this with a method
called on snapshot. Let's go over to all
usePizza composable. We can import this
from our firestore, so onSnapshot, I'll do
this inside of getPizzas. What we'll do now is
we'll comment out all of the existing code
which we use to grab our pizzas from the
database and we'll replace it with the
onSnapshot method. Passing on snapshots and
onSnapshot also takes in the pizza collection reference which is stored in dbPizzasRef. This is followed by a
function which we're going to run each time there is a
change inside the database. The function is going to take in the docs which is
returned back to us. Since we have a matching
name of docs just here with the docs which we're
looping over from before, we can now uncomment
out this section, put this out, and then paste
this inside of our function. This is doing exactly the
same action as before. We're getting back
the documents. We're looping over each, we're creating a
new pizza objects. Then underneath
for allPizzas ref. As soon as this updates,
it will then update all of the views or components
which relies on this data. So let's give this a
try over in the admin. So we'll jump into Add new
pizza, click on "Add", and as soon as we do this,
our application is updated, but we do see a small issue. Not only do we get
the free pizzas which are returned back
from the database, but these are also added to the existing two which
we already have. A simple way of doing
this is to clear it out our allPizzas array before
we get the information back. If we go into the try section, we can see we have
already clean out the existing value and certainly
it's been empty array. All we need to do is to move
this inside of our snapshot. Let's save this and refresh as our free existing
pizzas. Let's add one more. Click "Add" and
now our app is now notified of any changes. With this in mind, we
can also go back over to our deletePizza function where we could remove the
manual, getPizzas call. I'll take a look for our
deletePizza function. We no longer need to
manually call getPizzas, this is done for us. Now if we also delete pizza and our admin
is now still updated when we remove a pizza
and coming up next will also repeat this process
for our current orders.
53. Realtime Order Updates: To make the orders
update in real time, follow the same pattern as
we did with the pizzas. First, we go over to use
orders.js inside the Composable. At the very top, we need to import our own snapshot method. Then we can replace the existing get docs method
we'd on snapshot. We'll go down to getorders,
which is just here. We still need this queryData,
which is at the top. But we'll comment out
everything afterwards from get docs down to the bottom
of our try section. We'll call onSnapshot, which is going to take
in our queryData. Then a function to run
after each change. Again, to keep things consistent
and to reuse our data, we'll call the available
data inside the docs. Then our existing code, we can uncomment out,
put this out of place, place this inside
of our function. We no longer need these docs. We can cut these out
since we're getting these automatically passed
to our function. Then to avoid the same problem
we had with our pizzas, if you remember back in
the use pizzas file, we scroll down to overuse
pizza's function. Inside of here, we had
to reset the value of all pizzas to
be an empty array, since we had the
existing pizzas. Then after each update, the complete set of
pizzas were then added to our existing values,
rather than replacing. Do the same over in use orders, jump into the onSnapshot where we can access
allorders.value. This equal to an empty array. To test this, we need to open up our project in two
separate tabs. We'll place these
alongside each other. The reason we do this is because our orders are placed
inside the menu components, but we view these inside
the admin components. If we were to just
simply add a new order, then click on the "Admin", this would then automatically
pull in the current orders. We wouldn't see the
real-time functionality, but we can see
this if we go into the menu, create a new order. We have five orders,
click on "Place". You can see straight
away that this jumps up to six new orders. Let's try one more.
This jumps up to seven, and our latest order is
now down at the bottom.
54. Unsubscribing From Updates: When using onSnapshot, the process of listening for
our updates is continuous. However, though, we may no
longer need any updates. We do need a way to detach this listener to free
up all the resources. Doing this is relatively
straightforward. When we use on onSnapshot, it will turn back an
unsubscribe method, which we can then call
inside of our app. One use case for this could
be inside of the admin, for example if we go
down to the bottom, we are currently
listening out for any of our current orders. But as soon as we leave this page and go and
visit a new one, we're no longer need to listen
out for any new orders. We can detach this listener and free up any browser resources. To do this, go to
use orders.js file, where we can store the
unsubscribe function into a variable. As mentioned onsnapshot will return back an
unsubscribe function, which we can store inside
of a variable or constant. Currently the problem
is this unsubscribed constant is scoped
inside of this function. We need a way to
access this outside. One way of doing
this is to create a new ref just outside of here. We'll say const,
unsubscribe from orders. By default this will
be an empty ref. We can update this with
the value of unsubscribe. Let's jump into get orders. At the very bottom
of the tray section, just above the catch block,
we can grab our ref, which is unsubscribed
from all others and set the value to be equal
to unsubscribe. Now we have a unsubscribed
from oldest function, which we can call
when we no longer need to listen out
for any orders. We can do this inside
of a lifecycle hook called on unmounted. We need to import this
from the vue library. We already have onMounted, which is run as soon as the
component is mounted to the DOM and then onUnmounted
as soon as we leave. We'll place this in onUnmounted. This is going to run a function. We can test this out
with a console log just allows knowledge to work
in and we'll say unmounted. Then access our ref, which is unsubscribed
from orders dot value. We'll call this as a function. Save over to the admin
and we'll open up the developer tools
into the console. Then if we unmount
this component by moving to a different page, we see our console log. This should also call out
our unsubscribe function.
55. Adding New Users: Our application currently has the ability to
sign up new users. But any new user which signs
up can access our full site. This includes any
one of our pages, and more importantly,
the admin section. We need a way to set
up our users to only allow authorized admin
users into this section. Firebase does have a
solution to control users' access using
custom claims. If you're using a
production app, this is a great way to
go and you can find out more information with
this link just here. Custom claims do
need to set up on the server side for security. We can set these up using
Firebase Cloud Functions. But unfortunately,
Cloud functions require an upgrade
to a paid plan. For this reason, we'll be
using a different approach. This will involve
setting up inside of our database a users collection. Each one of these documents
will be linked to a user inside of our
authentication section, we'll use this user ID
as the document's ID, just like we have
here with our orders. But this also means
we can create a new user record which will contain some additional
information. One of these pieces
of information will be the user's role. In the past one, we've
created our pizzas and our orders in the
firebase.js file. We've created reference to
each one of these collections. Then we've used these to
create our new documents. For example, use baskets and we created a new order with
the addNewOrder function. We've used the addDoc method. This is pushed to this
reference, a new order. But the key part to
notice here when using addDoc is we don't push a
new ID for the documents. Each one of these documents
has a uniquely generated ID. But instead, we want
our user's collection to have the document ID which matches our user inside of the
authentication section, which is this one just here. Instead of using
the addDoc method, we're going to use
something called setDoc. This will allow us to
push some new data to a collection also set our
own ID which we want to use. To do this inside of
our firebase.js file, we export our database, which we can now use
in the use of.js file. Jump into this composable. Now we can import the
items which we need. The first one, we need to
import the Doc from Firebase. Also, the method which we just talked about which was setDoc. This is available from
firebase/firestore. We also need to import this
reference to our database. Import DB from our
custom Firebase file. This will be the
method which we use to create our new documents. But we also need a way to access the current ID of
the signed in user. Let's go down to the
sign-up function, which is just here. When we create a new user
with this method just here, it returns back an object. We can destructure
the user property to access the data
which we need. We can then use this
data just below to construct a new user object. Const, useObject. We can add some additional
information to our user, which will be stored
inside the database. First, a property
called createdAt, which is equal to a
new JavaScript data. We don't actually need
this date for our project, but it's always
useful to add this in case we need this in the future. We can also store
the user's email, which we can access
our userobject.email. Then the key part is a new
property called isAdmin. To begin, we'll set
all new users to have the isAdmin
property equal to false. Also still within this
try section as soon as the user has signed up and
this has been successful, we'll also close the
modal by setting the sign-in modal open
value to be false. Processing. Access the value
and set this equal to false. Just below our user object, we can then create a new
constant called newDoc. This will be the new document
which we're going to push the Firebase using
the doc method. Doc method needs to take it in a reference to our database, which is one which
we just imported. Then separated by comma, the collection name of users. The third value is
going to be the user ID which you want to use when
creating these documents. This is accessible
from our user. Access the UID property. Then to push this to Firebase, we can await setDoc. The documents, this is a reference which we
created just here. Pass this in as the first value. Then the second value is
the data for our record, which is stored inside
of the user object. Just to clarify what
we're doing here, when a new user signs up, this new user will be first created inside of this
authentication section. Then we're going to construct a new user object using this ID. Install this additional
information inside the database. But just before we test this, since we already have some users signed up in the
authentication section, which is not linked
to our database, we first need to clear
out these users. Let's leave this account. Also this one. Clear
out any existing users. This should not be
everything which we need. We have the new user object. We have our documents
was set in the doc. We should have all of
our imports database. We just also need to make
sure this is exploited too. Let's go back over
to our project. Create a new user , click Sign up. Over to Firebase and the
authentication section, we have our new user. Notice user ID, which
begins with 08. Yours will be
different, of course. Jump into the database as
the user's collection, which begins with the
same document reference. This is going to be
useful because when we first authenticate our user, we can have access
to the user ID. Then we can also access
the isAdmin property. To get started, what we're
going to do is we're going to click on the isAdmin field. Changes equal to be true. We have at least one admin user. Then we'll create a second user with the isAdmin value
set to be false. Sign back in, create
a new account. Sign this one up, there's our second account with
isAdmin set to be false. Now we have two users with
different access levels. We'll use this in our
app to only allow admin users into
our admin section.
56. Retrieving Admin Users: The first step to only allow an admin access inside of
our app is to retrieve the current user from
our database and check if there isAdmin
property is set to true. Since we will be
retrieving users, we can create a reference to the database collection
inside of our Firebase file. Just like we did with
pizzas and orders, we'll create dbUsersRef, which will point to
our users collection. Say this, and then into
the useAuth.js file. This tip our import, and we'll make use
of this very soon. Then inside of our function, we'll create a new ref
called userIsAdmin, which will be initially
set to the value of false. This default value
of false will be updated after we
create our function. This function is going
to grab our user data, will grab the logged
in user's ID, retrieve the record
from our database, and check if the isAdmin
property is set to true. Let's create a
function, this will be async, called checkAdminRole. First create an is
statement where we can check if our user data, which is this one
just here,.value, we can check if
this has the uid. We'll also add the
question mark because sometimes this
value will be null. Then inside we can retrieve the current user details
from the database. We can do this by
creating a document with the doc function which
we want to access. First, we'll access
the dbUsersRef, which is our collection, then we can access a
particular record by the ID. This ID is retrievable from
our userData.value.uid. Copy, paste this in. It is inside of a
constant called docRef. Then to actually call
for this information, we can await a function
called getDoc. We'll pass in our
document reference this inside of a
constant called user. Then we need to form two checks inside of an is statement. First of all, access
our user.exists, this exists method is
available on the user, and check if a
document is found, and then using the
double ampersand, we'll also perform
a second check, and this is to check if the isAdmin field is set to be true. We can also do this
on the user object, which has a method called data, which will give us
access to any one of these properties,
so data.isAdmin. If both of these
conditions are true, we can then update our userIsAdmin.value to
be equal to true. If not, inside of
an else statement, we'll set the userIsAdmin
to be equal to false, so paste this in.
Set this to false. We'll also need to import the getDoc method
from Firestore. Our userIsAdmin value will
be used in other files and components to check
if access is allowed. We don't need to return
this down at the bottom. Remember though all
we've done here is create a new function. We haven't yet called it
to run the code inside. The place to run this
code is going to be inside of the authObserver. Scroll down to
onAuthStateChanged and the code inside of here
will run each time a change of user
has been detected. If I have access to a user, if somebody is logged in, we'll then check the admin role. However though, if
the user logs out, we want to update userIsAdmin.value
to be equal to false. With this now all in place, let's go over to our admin view. We can access userIsAdmin, and remember, this
current check for our admin section is only checking if the
user is logged in. It's not actually
checking if they have access to this page. We can improve on this by
switching this out to be userIsAdmin. Let's try this out. Into the admin section,
let's sign in. First of all, this is the
email which is entered, which is not an admin. Add the email and
password, sign in. Good. This is not giving us
access to the admin section. Sign out and log in
with the admin user. Sign in and as soon
as this happens, we check the admin role, which is currently true. This will then update our
variable value of userIsAdmin, which gives us access. We can click Sign out. As soon as this happens, the AuthStateChanged
function will run, set our userIsAdmin
to be equal to false, hiding our admin
section from view.
57. Updating Regular Users To Admin Part 1: When we have this
project finished, we hand it over to the customer. The last thing we want to
do is to make the customer go into our database and update this admin property each
time you add a new user or to remove the admin access if an employee
leaves the company. What we're going to do
to improve on this is to create a new section
in the admin, which will allow an existing
admin user to update this. This process will
involve creating a new admin component, where the admin user can enter an email address of the user
which we want to update. Since this will be also
placed in this admin section, it also means only admin users
can perform this update. Once we search for
the user by email, it will then display
a message to say if the user is currently
an admin or not. Finally, we'll have a button
which will then toggle this admin role to
be true or false. First, let's create
our new component in the components directory and
into the admin, new file. We'll name this ToggleAdmin.vue. Then we'll create
our basic structure. Our basic structure
is going to be similar to the other
admin components. We can go into any one of these other admin sections
and down to the template. Let's copy this
template sections, give us the same structure. Paste this into our Toggle.Admin.
Let's simplify this. We can move the form,
so crop everything from the closing form to
the opening form tag. We'll keep these same
classes for styling, but just the level 3 heading to be Toggle Admin Privileges. We also need to update
this variable name. We haven't yet created this, but this one will be
called showToggleAdmin. Make this camel case.
We'll also use this in the other locations
and create this up in the script section subscript. This will also use
the script setup. Need to import ref from view and create our constant
which is showToggleAdmin. Wrap this in a ref with
a default value of true. This means, by default, we'll be able to see the
section inside of the admin, and then we can click
on the Hide button when we don't need
to see this section. Over to the views and into the
admin view to import this. Let's duplicate this one.
This is ToggleAdmin. This is also ToggleAdmin. Place this just
below our orders. Go to the Admin page, down to the bottom where we
can see other components. Back over to this
ToggleAdmin.vue. What we're going to do is
create two new sections. First will be a form
where we can find a user by entering
the user's email. We need a constant
to hold this email. This could be ref,
so const email, and the initial value
will be a string. We only want to
show this content which we're going to now place if the showToggleAdmin
is equal to true. Below the header,
create a new div. We can make use of conditional
rendering with the if. We'll only display this section if showToggleAdmin
is equal to true. Place now form. This form
will be pretty simple. We'll create a div, the class of form_group. Then inside here,
we'll add a label and also an email input so the
user can search by email. First, the label or email with a text to
find user by email. The input. This will have
the type of email ID. Also email a placeholder
text value of enter email. Then we can use V-model, which is going to bind the value to our
email up at the top. Next, just below
this div section, we'll also place in a button which is going
to submit this form. The type of button, this will then trigger a
function to find our user. We've not yet created
this function, but we'll add it in for now. [inaudible] click will prevent the default behavior and this
will trigger off function, which will soon create
the call, findUser. FindUser also needs to take in this email address which
we have stored up here. Input a text of find user. Then all the functionality
for this will be over and I will use
off composable. At the top, the
first thing we'll do is create two more constants. The first one is going
to be for a message. We'll call this the
toggleAdminMessage, which will make use of ref and an empty string to begin with, and a second constant
called selectedUser. This will be a container to hold a user which we search for. We'll set this equal to ref and the initial value of null. This toggleAdminMessage
will hold the value, which we'll say if the
user is an admin or not, or if they cannot be found
with the email provided. Let's create our function, so async called findUser. Let's use past the email of the user we want to search for. Placing a try section
and a catch as an error. Then we can get to work
inside of the try section. The first thing we'll
do inside of here is to clear any messages which
we may previously have. This is stored in
toggleAdminMessage.value. Set this to an empty string. Then we'll also create
a conditional statement and this will check
if the user is admin. Remember we currently have
this property set here. If this is equal to false, then the user is not allowed to update the admin property, so we'll return out
of this function. Remember those
things we actually call on this from
the admin section. This section should already
be protected anyway, but this is just an additional
check just in case. Next, we'll use the
Firebase query method. This will allow us to search
for a particular record if a single person is the user's collection which
is stored in dbUsersRef. Then we'll filter this down
using the where method. We want to check the email
property and check if this is equal to a particular
email address. This email address we
want to check against is the one which is passed to all function on this end, and this will check all
of our users against the email property
and check if it's the one which is passed
in from our form. What is inside of a
constant called query data. Then we can make a
request to this from Firebase by waiting getDocs. Parsing the query
data and then store this inside of a
constant called user. This user will be the
returned value from Firebase. Using this user object, we can then construct
a new user object containing only the
properties which we need. Remember when we've
looked at these objects, which we get back from
Firebase in the past, we get back lots of information, and most of it, we don't need. We create a new user object, place it in the ID, which
we can grab from our user, the docs, and getDocs is going to return
multiple documents, but we should only ever have one user with a unique email. For this reason, we can select the first document with
the index number 0. Select the ID and then
let's duplicate this twice. The second property
is the email. Change both of these.
The third property which we need will be isAdmin. Even as a simpler
user object which contains only the
properties which we need. We can then set our selected user to be equal to this object. Remember.value. This is the case if
this is all successful. If not, we can update the selectedUser.value
to be equal to null. Then also update our
toggleAdminMessage value to be equal to a string. You can place in any message which you
want to inside of here, but I'm going to say no
user found with that email. We also need to
import these methods from Firebase, which
we'll just use. We have the query, we have where, we have getDocs. In fact, just before we do this, we need to also call the data
method before the email. The same before isAdmin. This function will
extract the properties we need from each document, so onto these imports up at
the top from our Firestore, we have getDocs, the
query, and also where. Then finally, down at the bottom in the return statement, we can return our function
which was findUser, the selectedUser value and also the message which
was toggleAdminMessage. There we are. Save this
file and we just completed. Next, we'll import these and use these over in the
toggleAdminComponents.
58. Updating Regular Users To Admin Part 2: Previously we created a function to find a user by their email, and then we store this value into this selected
user constant. We'll now head into the
ToggleAdmin.vue component to import these at the very top. We'll import useAuth, and this is from the file
path of @/composable/useAuth. Then we'll extract and store these return values
inside of a constant. So we need find, we
need the selectedUser, and also toggleAdminMessage
is equal to useAuth, call this function,
then scroll down. Just under this form section, we'll create a second
section which will display the user if one has been found. It will also have a
message to show if a user is currently
an admin or not, then a button to trigger a function to toggle
the admin property. So create div for this section. We want to use the if to
only show this section if the selectedUser is equal to
a class of selected_user. To p element where I can
output the admin message. This was toggleAdminMessage and then a second p
element just below. This one is going
to have a string of text where the value of the user is currently set
as an admin or not admin. We'll say user, and
then pass it in at the selectedUser.email
is currently set as, and then we want to dynamically
set if this is or false. The way we can do this is by selecting our
selectedUser.isAdmin. Using the JavaScript
ternary operator, we can display the text
of admin if this is true. If not, a text of not admin. The final element
to go inside of this section is a button. This button is now for a
click, prevent the default. This will trigger a
function which we've not yet created called toggleAdmin. We'll come back to
this function in just a moment, but for now, the text of click here
to toggle admin setting. Save this and then we
can go back over to our use of composable, where we can create this
toggleAdmin function. Async, toggleAdmin, create the try and
the catch block. Just like we've done previously, we'll start with an
if statement to check if the user is the admin value. If not, we'll return out of this function because
they are not authorized. This is found from
userIsAdmin.value. This is false, we'll return out this function before
making any changes. Now we need to create
a reference to a particular document
which we want to update, store this inside of a
constant called docRef. Then we can construct this
with this Firebase doc method. It takes in a reference to our database we want to refer
to the user's collection. Then finally, we want to
find a document by the ID. We can do this by grabbing our selectedUser and
accessing the ID property. So selectedUser.value.id. Remember what we want to do with this document is to update the isAdmin property to be the opposite of what
it currently is. For this, we can write a Firebase method
called updateDoc. We will pass in the above
document reference. Then as an object,
we can set exactly which properties
we want to update. We only want to update
the isAdmin property to be the opposite of
what it currently is. We could do this by accessing our selectedUser.value.isAdmin
property and set this to be the opposite
of what it currently is. Once we've updated our
Firebase document, we'll then once
again call findUser, which will then retrieve and set our user data and
update any components. We also need to pass this, the user's email, which will have in
selectedUser.value.email. We'll just place it in
a simple console log into the error section. Also since we've used the
updateDoc method from Firebase, we need to import
this at the top and then return the toggleAdmin function down at the bottom. Back to the
toggleAdmin.view where we actually call in this
function from our button, which is just here,
but we still need to import this from our composable. This should be
everything now in place. Let's go over to the admin. Is email address is
currently the admin, but the other one is set to be false. Let's toggle this now. Click "Find user". This
user is unretrieved. We can see the property of isAdmin is set to be
false. Click "Toggle". This is then updated
to be an admin user, which you can also see
inside the console. This is not true. We'll then reinstate this back to be false, which we can also see
updated in the database.
59. Firebase Rules: This is now going to be
the part where we increase the security for our application
on the database side. What we'll do inside of the Firebase console if you go into the Firestore database, and then we have this
option called rules. This will allow us to add
some security rules to our database to determine
who can do what. We can set who want to
create, read, update, and delete data
from our database, and we can be more
specific about which documents or collections
these rules will apply to. At the moment, our database
rules are not secure. If we take a look, we have this much
here which matches a certain collection of
documents in our database. At the moment, this
is pointing to the outer entry point
of our database. Then nested inside we can add additional matches to
be more specific about which documents or collections the rules inside will apply to. This current setting
of document equals star star will match all the documents in
our whole database. Our database is not
secure since it allows full read and write access as long as we are within one month of the
database being created. This one month only rule
is a security feature, so don't forget to switch
off the full access. This will again protect
our database if we forget to come back in
and adjust these rules. To add our own rules, what we'll do is remove this inner match and begin
to write our own rules. I'll remove this and
leave the outer match, which again points to
our full database. Then we can create
a nested match. We'll start with our
pizzas collection, so /slash pizzas, and then we also have the
orders and also the users. We can add these rules too. Then remember, any one of these pizza documents is stored
in a unique documents ID. We can place in a variable and give this a name of doc Id. This will allow us to access the document ID inside
of this section. For pizzas, we'll start
by saying allow read. We allow read because
we're fine with anybody reading the
items from the menu. However, though, when
it comes to creating, updating or deleting pizzas, these needs to be controlled. We can also group these
operations together. We can say allow, create, update, and also delete. As long as some certain
conditions are met, we only want to allow
these three operations if a certain condition is true. This condition is they
are an admin user. The first thing to do is to check if the user is
actually authenticated. We can pass in an if statement where we can access
the request object where we can access the
off property and we can check if this is
not equal to null. This means that the user which
is making the request for this particular
documents inside of our pizzas is currently
authenticated. This is the first step. We have an authenticated user, or we also need to access a user's property of is admin
inside of the database. We can do this by using
a double ampersand, the chain on an
additional check. We can access a get function
provided by Firebase, which will allow us
to add a file path to a particular document. Inside here we need to
begin from the root path. The very outer match, which is pointing
to our database. We can copy this section
and paste this in. Then just like we've done here, we can add our collection name. We want to point to /users, and then a particular user Id. We can access the
current user Id from this request.auth object. Just after /users, we can insert a variable
with the dollar symbol, open up the brackets,
personally request.auth, and then we can access the uid. This takes us to the
particular document which you want to access. Then just after these brackets, we access the data
and then the property which is isAdmin, capital A. Once we access this
isAdmin property, we'll also want to check
if this is equal to true. Just to clarify, we're
do in two checks here. We're checking if first of all, the user is actually
authenticated, meaning they have
successfully logged in. The second check is to jump into the user's collection
and then check if the isAdmin property
is also set to true. Before we go any further, let's test these rules inside
of the rules playground. We can expand this
and we can create a different simulation
types to get our data, to create new data, to update and also delete
too. The location. This is the path which we
are currently checking. It's /pizzas /docId. We want to test this with
an authenticated user. Our authentication
provider is the password, then the Firebase UID. This is the user ID which
is making the request. I'm going to grab this. Without moving out
with this page, I'm going to right-click and open the authentication
in a new tab. Let's copy. In fact, we'll go into the
database, into the users. This one is a unique ID. Let's copy this and
this user is the admin. I'll paste this in. Click on Run and we making a get request. Here we can see
this is highlighted on the pizzas collection. Anybody can read this data. We would expect this to be fine. But the restrictions
lie when we create, update and delete data. To create data, optimistic given us an error.
Let's check this out. This is pointing to
our database match. The database is, a database, documents and I think this just needs a dollar symbol
since it's a variable. Dismiss. We also just need the regular brackets
to match this one here to insert the variable.
We'll try this once more. Run this width at the
Create. This is fine. Update. This is fine. Remember we are an isAdmin user. Also the delete works fine. Let's change this over to be the user which is
not authenticated, so isAdmin is equal to false. Copy this, paste this in. We are running a delete request. This is denied since
we're not an admin, but we should still be
able to get our data since anybody is allowed
to read our pizzas. Create, there should be a fail. Finally the update 2. Good, so now we know
this is working. We can repeat it for
other collections. But our orders, we want anybody to be able
to create an order. But any of the other
three operations should only be performed
by an admin user. Let's copy this match. Then paste this in just below. This one is for our orders. You can also rename this
variable if you want to. For here, we'll allow
anybody to create an order. We want to add some restrictions
when we are reading, updating or deleting.
The same rules apply. We want to check if the user is logged in and authenticated. We'll also get the
isAdmin property from the user's collection. This can all remain the same. Test this out. This is orders. That's well. We'll go to Create. Remember anybody can create a new order and we're
still with the user, which is not an admin.
It should be fine. Now we can test the
other three operations. Get, this is a fail as expected. Update. Delete. Then just to confirm, we'll jump into our user's collection, and grab the ID for the user, which is an admin.
We choose one. I'll go to the user which is an admin, can make a delete, an update, a create, and also a read. The final one we
need to do is to match our user's collection. They should allow anybody to create an account to begin with. We'll create a match or /users, pass in the variable of docId. Then we want to allow anybody
to create a new account, and we also want users
to read their own data. We'll allow read. But
we'll also restrict this. We'll also check if the user is authenticated. Paste this in. Also to allow the user to
only access their own data, what we're going to do
is to check if the user; which is available on
the request object is equal to the same user ID, which is passed
into this variable. The double ampersand
form a second check. Access request to auth the uid. This is the ID of the person who's actually
making the request. We want to check if this is
equal to the document's ID. Therefore, if both
of these match, the user is reading
their own data, but as well as users
reading their own data, we also want to allow
a user to be able to read any user's
data in addition. As well as this, we also want to wrap this inside brackets, and then perform a second check. Then also allow this to be
true if the user is an admin. We already know how to
get this information, and this is with this
get function just above. Copy everything from get right to the very end
where we check if the admin is equal to true, and then be careful to paste
this inside of the brackets. This will now always check if
the user is authenticated, and then either
check if the user is an admin or if they are
accessing their own data. In fact, this needs to be the JavaScript OR operator
rather than the AND, meaning either one of
these sides can be true. The last one to check
is to allow the update, and delete functions if they are an admin user. We'll allow. Combine these into update,
and delete sections. We'll add the same conditions
as any one of these above. We'll check if the
user is logged in, and if they're also an admin. Copy this line, paste this in, and this is now all
the rules in place. This could also be extended
to allow users to update their own information too
but this is fine for now. But it also includes
lots of duplicate code where we check if the user is an admin with the get function. We can improve on this by
creating reusable functions. We'll create two functions. One would be to check if
the user is an admin, and then one to
check if the user is accessing their own information. We'll put it all inside of the outer match. Put some space. Then we'll create
a function just like we'll do in JavaScript, so isAdmin, and then a second function
called isAccountOwner. IsAdmin, this is
the one which we use quite a few times now. We want to copy the request
auth is not equal to null. All the way to the very end of the get function, copy this. Go into the isAdmin function, and then we want to return
this value, so paste this in. This will mean our function
will return either a true or a false value. So isAccountOwner, this also needs to check if the
user is authenticated, so we'll return requests
auth is not equal to null. Then just like we did at
the bottom with users, we also want to check if
the ID of the person making the request is equal to the
document's ID, so copy this. Chain this on with the double
ampersands, paste this in. But remember here we're
accessing the document's ID. This document's ID is only available inside
of this section, so what we need to do is
to pass the function, the ID, and then we'll
replace this one. We'll add in this document's
ID when we call this function. We're
almost there now. What we need to do is to remove all of this cubicle code, and replace these with
our two functions. The first one is isAdmin. This is checking the
user is authenticated, and also if they
are an admin so we can remove all the code
from our pizzas' match. Just after these statements, remove all of this line, replace this with our function exactly the same for our orders. We're checking the same thing. Cut all of this out of
place. Put function in. The last stage is our users, and this is going to use
both of these functions. For the read, we'll cut out everything
after the if statement. This is going to make
use of both functions. If isAccountOwner, the ID will be the
document's ID from above. All is admin. This will perform the same two checks which we had before, and for the delete request, we're going to move everything
after the if statements, and simply pass in
the isAdmin function. This now looks a lot cleaner, and a lot more easier to read. The final thing is to test our user conditions
inside the playground. First of all, the location we are testing the user's path. We'll begin by copying the
user ID of the admin user. IsAdmin is equal to
true, paste it in. This one should be allowed
since we are the admin user. We'll also perform the
second check to see if the account owner is
making this request. Even though we are
not the admin, let's copy the non-admin user. Paste it in, in the code. This simulation is now denied. The reason this is denied is because although we
are an account owner, we're not accessing
our own documents. We're just accessing
any generic documents inside the user's collection. To test an access to
our own documents, we're going to
move the variable, and paste in the same
user ID as we have below. Run this. This now works since we are the account owner of the documents which
we are checking. Create. Let's try this out. This works. We can also
replace the variable. It should work for any
documents. We'll try one more. We'll try a delete. Run this as the non-admin user, and this should be a
fail. Switches out. Copy the admin user. Paste this in, and
this one is allowed since we are an admin user. There we go. This looks
like everything is working fine with
our security rules, and you can also update or play around with these
rules if you want to. But for now, our app
is much more secure, and we'll publish these changes.