[{"data":1,"prerenderedAt":439},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-food-delivery":121,"100-apps-100-hours-food-delivery-next":171,"sales-reps":187},{"items":4},[5,29,49,69],{"id":6,"title":7,"url":8,"page":8,"children":9},"522e608a-77b0-4333-820d-d4f44be2ade1","Solutions",null,[10,15,20,25],{"id":11,"title":12,"url":8,"page":13},"fcafe85a-a798-4710-9e7a-776fe413aae5","Headless CMS",{"permalink":14},"/solutions/headless-cms",{"id":16,"title":17,"url":8,"page":18},"79972923-93cf-4777-9e32-5c9b0315fc10","Backend-as-a-Service",{"permalink":19},"/solutions/backend-as-a-service",{"id":21,"title":22,"url":8,"page":23},"0fa8d0c1-7b64-4f6f-939d-d7fdb99fc407","Product Information",{"permalink":24},"/solutions/product-information-management",{"id":26,"title":27,"url":28,"page":8},"63946d54-6052-4780-8ff4-91f5a9931dcc","100+ Things to Build","https://directus.io/blog/100-tools-apps-and-platforms-you-can-build-with-directus",{"id":30,"title":31,"url":8,"page":8,"children":32},"8ab4f9b1-f3e2-44d6-919b-011d91fe072f","Resources",[33,37,41,45],{"id":34,"title":35,"url":36,"page":8},"f951fb84-8777-4b84-9e91-996fe9d25483","Documentation","https://docs.directus.io",{"id":38,"title":39,"url":40,"page":8},"366febc7-a538-4c08-a326-e6204957f1e3","Guides","https://docs.directus.io/guides/",{"id":42,"title":43,"url":44,"page":8},"aeb9128e-1c5f-417f-863c-2449416433cd","Community","https://directus.chat",{"id":46,"title":47,"url":48,"page":8},"da1c2ed8-0a77-49b0-a903-49c56cb07de5","Release Notes","https://github.com/directus/directus/releases",{"id":50,"title":51,"url":8,"page":8,"children":52},"d61fae8c-7502-494a-822f-19ecff3d0256","Support",[53,57,61,65],{"id":54,"title":55,"url":56,"page":8},"8c43c781-7ebd-475f-a931-747e293c0a88","Issue Tracker","https://github.com/directus/directus/issues",{"id":58,"title":59,"url":60,"page":8},"d77bb78e-cf7b-4e01-932a-514414ba49d3","Feature Requests","https://github.com/directus/directus/discussions?discussions_q=is:open+sort:top",{"id":62,"title":63,"url":64,"page":8},"4346be2b-2c53-476e-b53b-becacec626a6","Community Chat","https://discord.com/channels/725371605378924594/741317677397704757",{"id":66,"title":67,"url":68,"page":8},"26c115d2-49f7-4edc-935e-d37d427fb89d","Cloud Dashboard","https://directus.cloud",{"id":70,"title":71,"url":8,"page":8,"children":72},"49141403-4f20-44ac-8453-25ace1265812","Organization",[73,78,84,88],{"id":74,"title":75,"url":76,"page":77},"1f36ea92-8a5e-47c8-914c-9822a8b9538a","About","/about",{"permalink":76},{"id":79,"title":80,"url":81,"page":82},"b84bf525-5471-4b14-a93c-225f6c386005","Careers","#",{"permalink":83},"/careers",{"id":85,"title":86,"url":87,"page":8},"86aabc3a-433d-434b-9efa-ad1d34be0a34","Brand Assets","https://drive.google.com/drive/folders/1lBOTba4RaA5ikqOn8Ewo4RYzD0XcymG9?usp=sharing",{"id":89,"title":90,"url":8,"page":91},"8d2fa1e3-198e-4405-81e1-2ceb858bc237","Contact",{"permalink":92},"/contact",{"items":94},[95,101,107,113],{"id":96,"title":97,"url":8,"page":98,"children":100},"8a1b7bfa-429d-4ffc-a650-2a5fdcf356da","Cloud Policies",{"permalink":99},"/cloud-policies",[],{"id":102,"title":103,"url":81,"page":104,"children":106},"bea848ef-828f-4306-8017-6b00ec5d4a0c","License",{"permalink":105},"/bsl",[],{"id":108,"title":109,"url":81,"page":110,"children":112},"4e914f47-4bee-42b7-b445-3119ee4196ef","Terms",{"permalink":111},"/terms",[],{"id":114,"title":115,"url":81,"page":116,"children":118},"ea69eda6-d317-4981-8421-fcabb1826bfd","Privacy",{"permalink":117},"/privacy",[],{"description":120},"\u003Cp>A composable backend to build your Headless CMS, BaaS, and more.&nbsp;\u003C/p>",{"id":122,"slug":123,"vimeo_id":124,"description":125,"tile":126,"length":127,"resources":8,"people":128,"episode_number":132,"published":133,"title":134,"video_transcript_html":135,"video_transcript_text":136,"content":8,"status":137,"episode_people":138,"recommendations":152,"season":153,"seo":8},"9271a4fc-addf-4400-9591-f2f2ec07bd79","food-delivery","908328732","Food delivery apps exploded during the pandemic. But what does it take to actually build one? Follow along with Bryant as he has 60 minutes on the clock to build a backend, create a menu, and place an order in his Doordash clone.","76299639-810f-4814-9b76-b714b16b1974",63,[129],{"name":130,"url":131},"Bryant Gillespie","https://directus.io/team/bryant-gillespie",9,"2024-02-05","Mission: Food Delivery App","\u003Cp>Speaker 0: Hi guys. Welcome back to the next episode of 100 apps, 100 hours, where we rebuild or clone some of your favorite apps or publicly fail shamefully. Shamefully fail trying. Alright, I'm your host Brian Gillespie, developer advocate at Directus. Super excited to have you.\u003C/p>\u003Cp>I've got a special guest, my lovely wife joining me. I'm not sure if you can see her on my shirt here. This is a running gag between us where she basically gets me clothing with her face on it. So we'll include her in this episode. Today we're all about building a food ordering platform similar to DoorDash, Grubhub, or it seems like one of these platforms pops up every single week.\u003C/p>\u003Cp>The rules are very simple. We have 60 minutes to plan and build this application, no more, no less. And the second rule, the anti rule is we're gonna use whatever we have at our disposal, whether that's AI, UI frameworks, front end frameworks. We're going to be using Directus on the back end to generate this app really quickly. So let's kind of talk about the food ordering app before we dive in.\u003C/p>\u003Cp>You may have seen some of these before, we've just got a list of menu options, we want to be able to place those inside a cart and place an order for that cart, have somebody bring that food to us. So that's the basic gist of what we're building. Let's get started. Alright. So let's roll the timer and begin.\u003C/p>\u003Cp>I'm gonna just pull this to the side and let's pull up DoorDash just to take a look at this, right? So here's kind of what we're after. We've got different restaurants on here and if I click into one of these, like the bun stop shop, I have a menu of items here and we can add these to a particular cart, Choose some options. I don't know that we'll get to things like options. We'll probably just try to keep this very simple to begin with.\u003C/p>\u003Cp>We'll go ahead and add this to the cart and now I can go and place an order for this and have it sent to my delivery location. So great. That looks good. Let's discuss the functionality that we actually want out of this particular application, right? So I'm going to drag this down.\u003C/p>\u003Cp>In 60 minutes, I don't wanna bite off more than I can chew here, if we're doing cliches. So let's, you know, we've got the data model for the back end. We'll get that set up. On the front end we will show a list of menu items for restaurants. Allow place an order for menu items, and that's probably pretty good for an hour.\u003C/p>\u003Cp>I don't know. We'll see how far we get with it. The next thing I always like to do is to sketch out my data model. And one way that you could do this is obviously if you're trying to model another application, right, you could always kind of look at it and think through it. One of the things that I like to do, in this case I'm gonna just search for the DoorDash API because I know that they have other services that connect to this.\u003C/p>\u003Cp>And it looks like they do have an API that we could potentially use here. We don't wanna drive for DoorDash. Maybe it's this marketplace API, how to guides, menu flows and order flows. Here we go. Retrieve orders from DoorDash, set up a menu pull, set up a menu push, get a DoorDash menu.\u003C/p>\u003Cp>Okay. Yeah. So I can at least see a little bit of how they've got this structured, right? So I see there's a store that's associated to this. We have a menu.\u003C/p>\u003Cp>I'm assuming this is like a could be a breakfast menu or a lunch menu or a dinner menu. Maybe we have different menus that way. Then we have categories, I see, and then we have the actual items. Alright. So that gives me a good idea of of how they've solved this problem.\u003C/p>\u003Cp>And obviously, they're a large company, so they've got a lot of smart people on it. Do I want to take that at face value? Probably not. But for an hour, it seems like a pretty good model. Right?\u003C/p>\u003Cp>So let's start sketching this out. I'm just gonna make this full screen. We'll add a little box here. We've got, stores or restaurants. I'm not sure which one of those I'm gonna call it yet.\u003C/p>\u003Cp>Probably stores is easier. Then we have a we have menus. What else do we have underneath that? We've got menu items. Probably like categories.\u003C/p>\u003Cp>Right? I saw that on there. So we've got different items in the categories. Menu items, and then we're gonna have an orders table with probably some order items as well, orders items. Alright.\u003C/p>\u003Cp>Don't necessarily have to do this, but, yeah. Each menu belongs to a restaurant. The menu items belong to a category. So there's a relationship there. Just draw these arrows.\u003C/p>\u003Cp>This is more for me just visually to see how everything maps. Directus, our back end will make creating these relationships and these models very easy. Great. We probably have a restaurant that's attached to the order. We have a menu item that's attached to the order item.\u003C/p>\u003Cp>And then we've got, an actual person placing the order. Directus is gonna give us that. That will be our users table probably. User, users place orders. Okay.\u003C/p>\u003Cp>Alright. So as far as the structure of this, we got that. Don't worry about that little guy there. This seems pretty good as far as the structure. Right?\u003C/p>\u003Cp>So how do we start building something like this? I'm gonna pull up my blank instance of Directus. Directus is going to mirror all the changes that we make here inside the application, all of our collections. Those are going to be tables inside our database. All the fields are going to get represented as columns.\u003C/p>\u003Cp>So it's a great way to build out this functionality. And for me, I'm very front end oriented, so I'm a very visual person. So instead of writing migrations, this is great for me. I can go in and build this data model visually. And if I need to store it as code I can kick out the schema dot JSON file as well that others who are working on this project could apply.\u003C/p>\u003Cp>So let's go with stores. I really like that. If we just take a look at DoorDash, one of the things that I was curious about, okay. It looks like we have an ID for the store. They're not using anything like a slug.\u003C/p>\u003Cp>So it doesn't really matter for SEO purposes. Let me get back here. So we're gonna create this new collection. I can zoom in a bit. We've got stores.\u003C/p>\u003Cp>We've got our generated UUID. Let's go ahead and and just add these system fields here, just for status, date created. You know, they give us some of that functionality out of the box. So we're gonna have a name for the store. We're gonna keep this really lightweight so we could try to get, push orders into this.\u003C/p>\u003Cp>So we'll just do a name. Let's do a logo or an image. Let's just call it image for the store. Great. We probably got something like an address, probably.\u003C/p>\u003Cp>Right? Street address. We have a city. I'm just gonna duplicate this field, which is really nice. Street is a city, state, or region.\u003C/p>\u003Cp>We're trying to be international friendly here. And then we'll have postal code. Great. Cool. And then I may even just like group these together inside Directus using our detail group interface.\u003C/p>\u003Cp>We'll just call it address info. And we can even add a nice little map pin. There we go. So we'll group these together. Region and city, we can make those half width.\u003C/p>\u003Cp>Directus really allows you to customize the forms that your users will see. You know, on the front end here, I'm probably not going to give the people placing orders the access to the Directus Data Studio here. But, I certainly could give the restaurants owners or the store owners access to that to manage their menu, their store information. Alright, so we've got, this looks pretty good for our stores. Let's move on to the next one.\u003C/p>\u003Cp>Actually, take that back. Let me add a building, like a company, Office building. Building. Business. There we go.\u003C/p>\u003Cp>Alright. So that looks kind of like a store. Great. Alright. So next we are going to add menus.\u003C/p>\u003Cp>Alright. We will, I can again, I can add these things whether they are in draft mode or publish. You know, I may want to track when it was created, who it was updated by, etcetera. We'll give a name for the menu. And I'm just going to skirt the categories thing here, and just add these.\u003C/p>\u003Cp>Maybe we don't. But, yeah, let's do. We'll just go menu items. Right. And for menu items, again, I'll just populate these.\u003C/p>\u003Cp>They're always easy to remove later. We might want to have a sort field so we can order those items, not not actually place an order for them, but order them in terms of like sorting. What else? Okay. So on our menu items, if we look at DoorDash, we've got we've got a name for the item.\u003C/p>\u003Cp>We've got, description. Probably a price. Alright. Where's our DoorDash menu? What does an item have?\u003C/p>\u003Cp>An item has a name, it has a description, is it active, is it alcohol, is it bike friendly? I would argue that a cheeseburger is not bike friendly, but I'm sure this is on the delivery side. And then I see things like price, I see a tax rate, and then I see, like, some extras. For the purposes of this, let's keep it lightweight. We've got a name for this.\u003C/p>\u003Cp>We've got an image image for the menu item. We'll just create that. Let's have a description. I'm just gonna keep that as straight text. Toggle the sidebar within Arc as well.\u003C/p>\u003Cp>So you probably don't wanna do a WYSIWYG description here. And there's always risk if you're not sanitizing HTML content that is user submitted. So we'll just use text area. What else do we have? We have a price that we're going to set for this.\u003C/p>\u003Cp>That can be an input. I I know a lot of ecommerce options store the prices as integers, like incense. I'm just gonna use a decimal here And I could go into field creation mode if I want to, if I wanna control like the precision and things like that, and the scale. So I will just put 2 here. I only want 2 decimal places.\u003C/p>\u003Cp>We could do a formatted label and I can even add a prefix within Directus, which is really nice here. We'll do auto format, see what that generates for us. Cool. So we've got our menu items, we've got menus. Alright.\u003C/p>\u003Cp>Let's add a relationship between all of these. So we have a store, it has a menu or potentially menus, and then each menu has menu items. So if I go into menus, the designer or the recovering designer in me is gonna freak out if I do not add at least some type of icon for this. But let's add a relationship and this is gonna be really easy inside Directus. This is gonna be a one to many relationship because we have items on our menu.\u003C/p>\u003Cp>We have one menu, many items. So I'll go in and we'll call this, items is going to be the name of our field in the menu collection or the menus table and then our related collection is going to be menu items and the foreign key would be the menu. Right? So inside that menu items we're going to create a new field called menu. And I definitely want to show a link back to that item within the data studio.\u003C/p>\u003Cp>Now if you expand this into the advanced settings when you're creating relationships or just adding fields to the collection, you can see here that menu is not on menu items. That field does not currently exist. But Directus is gonna create that for us, which is really nice. Right? I don't have to mess with sitting down and doing migrations.\u003C/p>\u003Cp>As you can see, we're gonna build this functionality really quickly. Alright. So we have menus, we have a name, we have menu items. Let's go ahead and save this. If I go into our menu items, now I've got a field for menu, so that's going to show up.\u003C/p>\u003Cp>What else do we need to add here? Right? We're gonna need an orders table. So we'll say orders, we'll definitely wanna know who placed those orders, what's the status of that order, is it ready or not. And then let's go in and add order items.\u003C/p>\u003Cp>Again, naming things is very challenging, you know. Should this be called orders underscore items or order items? I like it this way. Totally up to you. Whatever you wanna name this inside your own.\u003C/p>\u003Cp>Maybe we sort those. So we've got orders, we've got order items. The order, if we just think through this, and of course add a great looking icon for it, the order is gonna be linked to a specific store. Right? We can only place one order from a store.\u003C/p>\u003Cp>So we'll go in, this is gonna be a mini to one relationship, and we've got this up over here so we can see everything we've got going on. This is gonna be the store. The related collection will be our stores, and Directus is going to create that relationship for us. And again, if I were to just open up Table Plus, so you can get a look at this. What is happening behind the scenes here?\u003C/p>\u003Cp>Directus is creating these relationships, it's creating my tables, it's creating all my columns in the database and it's also creating those relationships for me inside the underlying SQL. So if I were to rip out Directus one day, you know all my SQL data is still pure everything inside my database. Alright, so we've got an order, we've got a store, we've got a, a customer. Right? We'll call that customer.\u003C/p>\u003Cp>This is gonna be the Directus users collection. We've got an address for that so we could go in and do street address, city. I can also go back to that other collection and just duplicate these across, which is probably what I should have done. But no worries there. Alright.\u003C/p>\u003Cp>So we'll add these fields and again, I could group these together using our detail group. We'll call this address info. You know, we may again, we're gonna have a front end where users place their order, but on the back end we could fulfill these orders through the Directus interface as well, through the app. And that again that helps me just move things along faster so that I don't have to build an admin interface. I could just use Directus here to manage all of that data.\u003C/p>\u003Cp>Alright. Orders and then we have our items, right? So we'll go in this is again, this is gonna be a one to many relationship. There are one order or there's one order that has many items. Because I know yeah.\u003C/p>\u003Cp>At least at first, or when you're modeling data this way, it can be kind of confusing of which relationships to use. So this is a one to many from the orders table to the order items. I'm gonna call this items as well, and the table is gonna be order items, and then we're gonna use a foreign key of order. I always like to show a link to the item so I can open that up inside Directus and we'll just hit save here. Great.\u003C/p>\u003Cp>We got a store, we got a customer, we got a street address, we got a status that we can go in and adjust. Alright. This is a draft order, published order, an archived order. That doesn't make a ton of sense. Alright.\u003C/p>\u003Cp>So let's just nuke those. We'll call this a new order. We'll call it, In Progress or Being Made. I don't know. Again, naming things is part of the one of the hardest things inside development.\u003C/p>\u003Cp>We'll say ready for pickup for pickup. And what? Delivery? I'm in out for delivery. Delivery.\u003C/p>\u003Cp>Out for delivery. Out for delivery. Let's just say delivered. Right? I think those are just a quick rundown of what the different states could be for a specific order.\u003C/p>\u003Cp>Is it new? It's being made? It's ready for pickup? Somebody comes and picks it up, it's out for delivery, and it's delivered. Right.\u003C/p>\u003Cp>Again, it's not as simple as this. Right? You've got a driver and things like that included, but we're keeping this very simple so we can actually dive in to the functionality. So on the order, we also probably have like a total for the order as well. Right?\u003C/p>\u003Cp>Order total Order total that would sum up all of the individual line items. This is gonna be an input field. The schema though, we probably want the type to be decimal, just because that's what we have for our menu items. Great. Again, you could store this as synths and you know, do all of that on the front end as well.\u003C/p>\u003Cp>What do we what else do we have? We've got the order items, we got the order, and then we're gonna add a relationship here to the menu items. So this is gonna be a mini to one relationship because we can only add 1 menu item to one order item. That's great. So we'll call this the menu item.\u003C/p>\u003Cp>Sounds great. Menu items. That's our related collection. We'll hit save. What do we have next?\u003C/p>\u003Cp>We probably have a price for this specific item as well. Again, that's gonna be a decimal. I can adjust the number of decimal points. Great. And then one of the other things that I saw on DoorDash here, this is not a an actual order.\u003C/p>\u003Cp>Let's see if we can find a an order. Right. What does an actual order look like? Receive orders from DoorDash, blah blah blah, notable fields. Let's just look at the UI.\u003C/p>\u003Cp>So I go in and I add this. I can add special instructions. Right? So let's add a field for special instructions. And we also probably have a quantity.\u003C/p>\u003Cp>Right? I can go in and adjust the quantity of these. So if I want 3, 4, 5 hash browns, I don't have to add a separate item. So we'll do quantity. That'll be an integer.\u003C/p>\u003Cp>You can order half of a hash brown. I guess you could. I'm not sure that they would deliver it. So we have quantity, menu item. Let's show the actual order.\u003C/p>\u003Cp>This little hidden I there tells me that that is not displaying to the person when they pull up this form. But there we have the order, we have the menu item, we have the quantity, we have the price, and we probably have an something like amount or subtotal, item subtotal. Again, this is where it gets tricky as to what you name these things. So the subtotal here would just be the quantity times the price, that's going to equal our item subtotal. Alright.\u003C/p>\u003Cp>So this feels pretty pretty good, right? That's, kind of what we had. We just totally erased categories for now. We just wanna get to this core functionality. We've got, roughly 30 minutes and some change to evaluate this, right?\u003C/p>\u003Cp>So first, let's go in. I'm just gonna pull this up side by side, and I'm just gonna steal some of these items. Right? Let's go in and I I don't wanna do McDonald's. I've got my address set to Yankee Stadium here in the Bronx.\u003C/p>\u003Cp>If you work at Yankee Stadium, I'm sorry. We're gonna deliver some food to you. May not be something that you like. Alright. Let's go with wallet friendly.\u003C/p>\u003Cp>Jimbo's Jimbo's Hamburger House. Looks good. Let's go in and add a new store over here. So inside Directus, you can see how that form, that data model that I set up, how this actually looks when we are managing this. So again, you're probably for something like DoorDash or an order delivery app, you're probably not going to give the person placing the order, like the end user, access to this.\u003C/p>\u003Cp>But this interface is so beautiful, I could certainly give the store owners that wanted to sell their stuff on our platform access to this. So this is 252 Saint Ann's Avenue, Bronx, New York. I don't have a postal code, we'll just say 5555. Right. And then I can see I've got a image here.\u003C/p>\u003Cp>I can just copy that image address and Directus makes this really easy to import images from a URL. Boom. Great. Maybe I even wanna show this image. When you are creating your data models, you've got a ton of options as to how these things display inside the application.\u003C/p>\u003Cp>So I could go into this image field and I could display a tiny image preview. I can even make it show up as a circle like what they have there. Great. There we go. We've got Jenbo's Hamburger Stand, Hamburger House.\u003C/p>\u003Cp>Let's go into our menus. Right? So we'll create a menu. Let's call this the lunch menu. We're missing that relationship though.\u003C/p>\u003Cp>Right? Where did we did we actually create a relationship between menus and stores? We did not. So let's do that quickly before we get too carried away. We will create a many to one relationship on the menus back to the store.\u003C/p>\u003Cp>Stores, and then I can open this up and in our relationship field, I can add that back as well. So here's our menus. Cool. Great. Alright.\u003C/p>\u003Cp>So now I've got that relationship. I go into the menu, I can pick the store, there's Jenbos hamburgers. And let's start adding some items from this. Right? We've got our silver dollar pancakes.\u003C/p>\u003Cp>This looks good. Items are silver dollar pancakes. I can copy that image address and have direct us pull this in. We've got some additional options here. I don't see any like descriptions for those.\u003C/p>\u003Cp>Not a big deal. The price is 6.50. So we'll just input that. Alright. Next, let's add some other options.\u003C/p>\u003Cp>We've got a Philly steak bacon wrap with fries deluxe. So we'll just add that here. Looks like I don't have an image for that one, but I do have a description. What is the price for that? It is 12.50.\u003C/p>\u003Cp>Great. So we'll just input some of this data. Why do I have an image on this? Jenbo, you need to get some images on DoorDash, my man. So we'll just do a Philly steak and cheese.\u003C/p>\u003Cp>Who knows where we're ripping this from? This looks good. Copy image address. That one's not available. Philly steaks.\u003C/p>\u003Cp>I guess this is gonna be all sorts of copyright infringement here. This is for entertainment purposes only. Any lawyers watching this, just so we know. Maybe I could get Nat. Nat edits our videos.\u003C/p>\u003Cp>He's amazing. Nat, maybe you could place a disclaimer on here somewhere. Alright. So we've got a couple of menu items. What else do we have?\u003C/p>\u003Cp>We have waffles. We have tex mex. Good enough. Alright. So we don't have any orders.\u003C/p>\u003Cp>We don't have any order items, but at least we have a couple menu items and we have a store. Great. Let's dive into something on the front end. Right? What am I using on the front end?\u003C/p>\u003Cp>I've got a just a basic Nuxt starter kit. I'm with you guys, so I I like coding in Nuxt and Vue. And then I've just got this simple app. Right? So if we go back to DoorDash, we've got to get some kind of view where we have a list of restaurants.\u003C/p>\u003Cp>And how do we do this? Right. Nux has a Nux 3 has this use async data composable. So let's just call this stores, that's our key. And then inside this starter kit, I've just got a module that communicates with Directus.\u003C/p>\u003Cp>I've got a little composable here that auto imports items from our, like the different methods from our SDK and allows me to communicate. So we'll just adjust this. This is going to be our stores. And here, let's just maybe, we're not gonna flex this. But let's just log our data and see what we're getting back.\u003C/p>\u003Cp>So I could do data. Maybe I wrap this in a pre tag. Bada bing bada boom. I should be able to get this data. We should be reading something from stores, right?\u003C/p>\u003Cp>I'm not. So the issue here is that I have not set any permissions. Directus comes with rule based access control, which is pretty amazing that I can configure all this via the UI and it applies in real time. But we've got 2 roles by default. The administrator role, which I can apply to users who have full access to the data model, all the different settings, and then we have the public role.\u003C/p>\u003Cp>So the public role, we want to give access to, probably not So they could probably see the stores, they could see the menus and the menu items. We don't want to give them ability to see any of the orders or create orders. Right? You want to log in for that. So we might as well go ahead and add a new user here as well.\u003C/p>\u003Cp>So the user role should be able to place orders, they should be able to view orders, with the exception that they can only see their orders. Right? We don't want them to create menu items, but one of the other nice things about the rule based access control in Directus is I could set up custom permissions. Right? If I am a user, I I shouldn't be able to see everybody else's orders, I should just be able to see mine.\u003C/p>\u003Cp>So I could do something like this, where in this read permission setting for our orders collection, I could go in and control the items that are available. Right? So if I've got a user that created this, has to equal dollar sign underscore current user. So basically this is a little syntactic sugar. This will, fetch the current user ID for you and set up permissions so that if I'm logged in as a specific user, I can only see my orders.\u003C/p>\u003Cp>That's super nice. Alright. Cool. Let's go back to our front end. Now I refresh.\u003C/p>\u003Cp>I can actually see some data. This is good. Right. Now let's go in and actually flesh this out. We've got div, maybe we've got a grid of, what, 3 columns.\u003C/p>\u003Cp>I I love using Tailwind for the styling. Grid calls 3, maybe a gap of 4. Alright. And then we have I'm using the Nuxt UI library just, as a cheat codes here. They have a card component that I can use here.\u003C/p>\u003Cp>We'll just say ucard. We'll do a v 4. And it looks like, GitHub Copilot is propelling me forward here. We have the store dot ID. What are we gonna drop inside the actual card?\u003C/p>\u003Cp>We probably have the what do we wanna do here? We wanna add the image up top, and I can see there's like a template, like a header slot and a base slot, but maybe we just stick it all in the same one. I'm also using Nuxt Image in this as well, which is really nice because, I don't have to create any it will automatically optimize the images for me. And Directus will do that via the API as well, but this is kind of nice just because they use the same underlying technology, the same library. So let's do height 48, width 48, object cover, Jinbo.\u003C/p>\u003Cp>We're not getting the actual image. Right? What's going on here? So if I check our network requests, if I just slide this over to images, we're getting a 403 forbidden on the images as well. Right?\u003C/p>\u003Cp>So images and assets are a system collection inside Directus. So I've got Directus files, that's where all of our assets get stored, aside from the actual image on disk, we're keeping that library of them. I forgot to enable control for those. So I can go in under system collections, I'll just enable access to those files. Let's go ahead and give this user access to those as well.\u003C/p>\u003Cp>Where are you? Files. Okay. They've got access. That's great.\u003C/p>\u003Cp>I probably shouldn't have closed Directus out entirely. But now I could see I've got this, right. And for the width, let's just do with full instead of 48. Okay. Object cover.\u003C/p>\u003Cp>It's a little pixelated. It doesn't look great. But, next let's go in and add but we're going to add a title for this, right, the store dot name. Let's make this bold. And maybe 3xl.\u003C/p>\u003Cp>Jinbo's hamburgerhouse. Okay. Alright. Up top here, we can add a h one tag, restaurants or h 2. Or maybe this is a h one.\u003C/p>\u003Cp>Bold text gray 600. Maybe we add a bit of margin to that. It's a little much, maybe mb 8. And then here for this, I think we've got a container here as well that we might use. I think it just applies some padding.\u003C/p>\u003Cp>You container. So we get away from Okay. Yeah. Now we're not pressing on the edges of this. Another one of my favorite tools is Tailwind UI.\u003C/p>\u003Cp>If you build apps and you like speed running things like I do, super handy, right. I could go in and I've got store navigation here. I could just basically lift one of these store navigation components if I wanted to. So let's look for like a header marketing elements. We've got a header here.\u003C/p>\u003Cp>How do I just get something really simple? This looks pretty good. We'll just copy this. I'm gonna create a new component. I'm gonna call it the header dot view.\u003C/p>\u003Cp>We'll do a view component. What do they got? They've already got some of this for me. Let's just copy it and see how far we get. I could go into our default layout and maybe I want to add the header here.\u003C/p>\u003Cp>See what that gets us. Failed to resolve the imports for the icons. They're using a different icon set than I am. I'm gonna delete these out. And I'm not even really super concerned with this navigation either.\u003C/p>\u003Cp>Let's just make this our cart. We'll change that to a button for now. Type equals button. What do we get? Are we getting anything?\u003C/p>\u003Cp>Yeah. Cart MX button. Why is it not showing that? Oh, this is probably the mobile menu button. Hidden flex type gray.\u003C/p>\u003Cp>Justify end. Is it not showing? Okay. There it is. Product login.\u003C/p>\u003Cp>Let's do something like MD here. That way this is not hidden. Empty. Refresh. Okay.\u003C/p>\u003Cp>So I got login. Let's just change this to cart. Again, I'm gonna change this quickly to a button because we are going to use it to display our cart at some point. Alright. Do we even need product?\u003C/p>\u003Cp>Let's just remove that. Forget about it. Okay. So we've got, we got a menu or we've got a restaurant. Right?\u003C/p>\u003Cp>Now we need to be able to click on this specific restaurant. We go into that restaurant and then we're gonna display a list of all the items from that specific restaurant. Alright. So let's go in and create a new route. We'll call it restaurants or stores in this case.\u003C/p>\u003Cp>And inside that directory, I'm gonna do something like this where I use, what are those called, just brackets? Yeah. We'll just use a dynamic route here, vcomp ts. And I could copy this async data call here. And let's call this our actual store.\u003C/p>\u003Cp>So we've got our stores. We're gonna read. We don't wanna read all the stores. We just wanna pick up 1 individual store. And we're gonna do that via the route parameters.\u003C/p>\u003Cp>So we'll do this route equals use route, which is just a composable that ships with view 3. And here should be route dot ID, and then as the third argument we can pass in, you know, like, hey, I want all these specific fields or not. Again, anytime I'm just like testing this out, I may add something like pre and just log that data just to see what I am working with initially. Okay. So we'll go back to our index page.\u003C/p>\u003Cp>We wanna make this, clickable. Alright. So maybe we wrap this in Nuxt link. The 2 is gonna be, let's say, dash stores store dot ID. Alright.\u003C/p>\u003Cp>Let's see where that gets us. Now I can click on this and it goes to the store. Right? You can see that we had a route change. Store, for the let's do something like this.\u003C/p>\u003Cp>Async data for the the caching, it uses a key. So I could pass the key like this and do route dot ID. Let's see if that gives us what we're after. I refresh. Why am I not getting this?\u003C/p>\u003Cp>Oh, route dot params.id. Duh. Silly rabbit tricks are for kids. Alright. So here's the data for the individual store.\u003C/p>\u003Cp>Alright. If I go back, you can see the difference. I'm getting an array of items here because I'm calling the Directus API via read items. When I click on Jenbo's hamburger house, I get just the actual item because we are using read item here. We're picking that up by the param ID.\u003C/p>\u003Cp>Now, how do I get access to this actual menu item? Right. A couple ways I could do this. I could make another call or one of the many things that I love about Directus is the ability to get all the related fields in a single call. And I'm going to use this wild card syntax here just so we're we could take a look at it.\u003C/p>\u003Cp>In production, you probably wouldn't want to use this. But if I wanted to, I could just fetch the ID. Right? So it's very GraphQL like and then I can tell it specifically the fields that I want and I can go in and do something like this where I get menus dot star. We can see okay, here's the the different menus.\u003C/p>\u003Cp>I want to drill down one more layer and I want to get menus dot items dot star. That is a wild card for all of these items and that could give me everything that I need to render this on the page. Right? I could go one further if I'm storing like metadata, like alt tags or title text or something on the image. Itself, I could drill into that further.\u003C/p>\u003Cp>Really all I need to render an image is just the ID, but great. So now how do we set this up? Let's go in and we'll add a menu. Let's add an h one. This is gonna be the store dot name.\u003C/p>\u003Cp>Class 3xl. Font bold. Maybe we don't make this bold. Let's see what we got. We got Gino's hamburger's house.\u003C/p>\u003Cp>Maybe we dress this up a little bit. What does it look like inside door dash? Got like this nice header image up here at the top. We don't really have one of those, but I can at least show the, logo for the store. Right?\u003C/p>\u003Cp>Let's make it look nice. Store dot image. Is that what we called it? Class let's make it square. 48.\u003C/p>\u003Cp>Let's do 24. We'll make it smaller. Right. With 24, object cover rounded full, this should give us the image there. We could probably wrap this in, div.\u003C/p>\u003Cp>Just flex that. Class equals flex to a gap of 2. And then within this, we might wrap that in a div as well. Okay. Cool enough.\u003C/p>\u003Cp>We've got that. Now let's add these actual menu items. Right? So I've got this array of menu items. Let's just take a peek at how we're doing on time.\u003C/p>\u003Cp>Roughly a little less than 20 minutes. Are we gonna get this done or not? We shall see. Right? So I'll go in.\u003C/p>\u003Cp>Let's just do a grid of menu items. Great. Got a gap. Go co Copilot go. Right?\u003C/p>\u003Cp>We're use that same ucard component. V for menu and store menu. I really like GitHub Copilot. It's great for like really simple auto completion things like this. One of the things that I can tell you is make sure whatever you do, you verify all of this before you actually use it.\u003C/p>\u003Cp>Right? Because here I'm getting okay. This is not actually working at all. So v 4 menu and store menus, we really just want the first item. Right?\u003C/p>\u003Cp>We're gonna do the items in store menus 1, the first item in there. That's gonna be item dot ID. We don't really need the store. Right. This is kinda weird.\u003C/p>\u003Cp>This is where GitHub Copilot gets you into, a lot of trouble if you just blindly do whatever it's it's saying here. And this is still not coming up right. Item in store dot menus dot 1. This would be the actual menus dot items. There we go.\u003C/p>\u003Cp>Okay. So now we could see we've got our silver dollar pancakes, we've got our Philly steak and cheese. Let's make this smaller. And then at the bottom, we're going to add our price. Don't forget the item dot description.\u003C/p>\u003Cp>Then we also need a price. Alright. So item dot price. Let's just see what that looks like. We can add a dollar sign.\u003C/p>\u003Cp>We've got our silver dollar pancakes. Cool. Maybe we wanna give a little bit of gap between all these. This is not gonna be a Nuxt link either, right. We want this to be a button type equals button.\u003C/p>\u003Cp>We'll add some functionality into this in a moment. Class equals text left. Okay. Maybe we give it a flex call gap 2, Give a little bit of spacing in there. And make the width full.\u003C/p>\u003Cp>Well that, yeah, there we go. That gets us what we want. Let's make the gap larger. Okay. Cool.\u003C/p>\u003Cp>So we got some menu items. Right? We want to click on these menu items and add them into a cart. So what are we gonna do, how are we gonna manage this cart? Because it could be we want to maintain this cart across like page load and also across different restaurants or different pages as well.\u003C/p>\u003Cp>So for now, I think I'm just going to simply use a composable for this. I don't have Pina installed, which is a really great state library for Vue. You know, Nuxt has a built in used state composable that you could potentially use as well. But I like the idea of just adding a composable to manage this. So I'm gonna add a new folder here in the root directory.\u003C/p>\u003Cp>I'm gonna call it composable Composables. Nuxt will actually auto import these. And let's call it use cart dot ts. Alright. We're going to export a default function, use cart.\u003C/p>\u003Cp>And up here at the top, let's just take a look at our cart structure. Right. What do we have? What do we want to send? So we've got the orders.\u003C/p>\u003Cp>If we take a look at our data model, right, we have the store, we have the customer, we have that's gonna be our individual user. So we've got to log in as a user. We got our items. We got a total for that. Okay.\u003C/p>\u003Cp>So let's go in and add a list of items. That's gonna be an array of items. Right. What else are we going to have inside this composable? Right.\u003C/p>\u003Cp>We've got our items. We're pulling this outside of the function definition because we want to store this. If we call this use cart in multiple places, we still wanna access the same items. Alright. So this is gonna return the items for that cart.\u003C/p>\u003Cp>We're gonna add those cart the order total. Alright. So we have the order total that's going to be computed. That would be a return items dot for dues dot item dot price. That's perfect.\u003C/p>\u003Cp>Thank you, Github. Got an order total. And then we've probably got a function for, oh, let's go back up, async function, place, yeah. Let's add to cart. We're just gonna push an item to the cart.\u003C/p>\u003Cp>We gotta remove from cart. Look at GitHub Copilot. Again, like, please take this with a grain of salt, whether this is actually wise or not. Index of item, items value dot splice. Index self item.\u003C/p>\u003Cp>Okay. And then if we've got, and these actually probably don't need to be async functions. And then we probably got an async function for submit order. Alright. Submit order.\u003C/p>\u003Cp>Alright. This is gonna be constant data equals I don't know if that's gonna be data or not. We'll do await. We'll do use directus. Then we are going to create an item in the orders table or orders collection, the total for that order total, ordertotal will be the order total dot value.\u003C/p>\u003Cp>Then we have our items, which are going to be, should just be items. Alright. So we could do something like, nope. We're not gonna do that. We are going to do unref items since that is unrefed.\u003C/p>\u003Cp>We've got the store info and this customer field, inside direct us, I can save the current user on create. So let's do that. So the store is going to be where are we gonna get the store from? Maybe we're gonna pass that in the function. Store ID, that's a string.\u003C/p>\u003Cp>Store ID. Alright. Is this cool? We're gonna return these other functions from our composable. Let's see how we do.\u003C/p>\u003Cp>Alright. So now if we go back to our app, we've got the hamburger house. Let's just remove this, comment that out. Alright. So we got our menu, We want to push these items into a cart.\u003C/p>\u003Cp>Where are we at time wise? We got 10 minutes left. Are we gonna actually get these into a cart or not? So here inside this specific, page we might do what we are going to return. Let's take a look at our composable.\u003C/p>\u003Cp>Right. We've got our order items. We've got our items in the cart. Let's call the we're gonna get items. Let's call these cart items.\u003C/p>\u003Cp>Add to cart. Remove from cart equals used cart. K. And then can we hey. Let's just drop in our cart items here just to see what that looks like.\u003C/p>\u003Cp>Cart items. It's It's not showing anything for our card items. Are we getting an error? Let's actually take a look. So we'll go to ID.\u003C/p>\u003Cp>Cart items equals undefined. Oh, it's because I'm getting items. There we go. Alright. So we're showing just an array.\u003C/p>\u003Cp>Let's just do this. I'm just gonna give this some padding. BG gray, 50. Let's make it 100. This is our, p tag cart items.\u003C/p>\u003Cp>Okay. Alright. So now what we want to do on each one of these, right, if we click an add to cart button, which we we don't necessarily have. Where is that? Right?\u003C/p>\u003Cp>Why do we not have a button for add to cart? Maybe we don't even put that. That shouldn't even be in here basically. So let's move this up. We'll just take the class.\u003C/p>\u003Cp>We'll apply that to the card itself. Why is it not doing what we want? Gapspacey2. Why are you not doing what I want you to do? Alright.\u003C/p>\u003Cp>I'm not gonna stress over it. We'll just wrap it in a div because this will be the div task adventure. I know all the non tailwind CSS guys are probably freaking out at this moment, but okay. We'll use this u button component provided by Nuxt UI and we're going to click add to cart. We're going to add that item to the cart.\u003C/p>\u003Cp>Yeah. Close enough. Right. So if I click add to cart, we can see we've got our cart item here. It's not necessarily what I want.\u003C/p>\u003Cp>Right? The data structure is not entirely what I want. So let's actually just define it here. The item ID, we want this to be our order items. Alright.\u003C/p>\u003Cp>So we go into order items, We got the menu item. Let's do that. Menu item. Okay. Special instructions.\u003C/p>\u003Cp>No onions, please. Yeah. We'll just automatically populate that. The quantity, let's set that to 1. And then the item subtotal, we'll just say item price.\u003C/p>\u003Cp>Alright. So now if I refresh, does that give us kind of the structure that we want? It's still populating the entire menu item. We just want the menu item item.id. That should give us what we need.\u003C/p>\u003Cp>Okay. Does that match the right structure? Great. Where we at on time? We got, 5 minutes or so.\u003C/p>\u003Cp>Let's actually get this order together. Right? Alright. So inside our header, maybe we could display our cart items there. I really wanted to actually show the submitting to Directus.\u003C/p>\u003Cp>So knowing that I am short on time, right, we could go in and add these items. Let's just try submitting it right now and see. Alright. So we got our cart items. Let's go in.\u003C/p>\u003Cp>We've got new button, place order, and we'll call this submit order. Are we passing we're passing that store ID as well. Alright. So this isn't probably the best particular type of design, but it will get the job done. Let's do a large button.\u003C/p>\u003Cp>At click is submit order and then we've got the store dot ID that we're passing to this. And then let's go in and just like what does our submit order look like? This should be probably like try catch. So we could catch any type of errors. Response.\u003C/p>\u003Cp>If we catch an error, we want to return the response. Great. Submit order. Oh, that looks a little rough. Let's move that over here.\u003C/p>\u003Cp>Oh, I put I did I totally put that in the wrong spot anyway. Submit order and let's just hide this if the user is not logged in as well. Right? We don't want them to be able to place an order if you are not logged in. So this composable that I have, I want to say, has a or like this module that I set up has some of this already built in.\u003C/p>\u003Cp>So this will be v Where's the directus auth? Directus auth, user. So we'll do something like this where we get constant user equals use direct us off if there is a user. Okay. Alright.\u003C/p>\u003Cp>And if there's not a user, let's do a u button. V of no user, we are going to I think I could do this with it, where we have login to place an order. Login to order. And we will do what is it? 2 auth, I don't even need that.\u003C/p>\u003Cp>We do to auth/auth/login. Login to order. Okay. We've got our user. I didn't set up a user, but let's create one now.\u003C/p>\u003Cp>This will be our user. Test usertest@example.com. Let's give them a real secure password. We'll do password here and then I am going to select the role of user. Great.\u003C/p>\u003Cp>Save it. Now I should actually be able to log in. So test at example dot com, Password of that login. Tried to log in to the wrong spot. Where's the redirect for this?\u003C/p>\u003Cp>Use direct us off. Is this going to be in my Nuxt config? We want to redirect to auth login, not portal. We want to redirect to the index page. Oh, we're gonna we're gonna be cutting this one really really close.\u003C/p>\u003Cp>Come on. Alright. So we go back. Do we have we've got our menus. We don't have any cart orders.\u003C/p>\u003Cp>It looks like I'm still am I still logged in? Do we have the cookie? We've got okay. So we've got a session token here, so it's still showing me as logged in. If we hit add menu items, I hit place order, what happens is we can see the order posted.\u003C/p>\u003Cp>If we go inside Directus, do we have that specific order? Yes, we do. So we've got here's the store information, here's the order, we've got the item subtotal. We don't have the prices not being populated. So I didn't item subtotal should be item dot price.\u003C/p>\u003Cp>Why is that not populating correctly? I didn't add the price either. But item dot price. Cool. I'm calling this a win.\u003C/p>\u003Cp>So we've got what have we done? We've set up our data model within an hour. We've got all the items coming through. I could go in and clean this up, certainly, and that would be my next steps. But we've got a really simple UI here on the front end that with probably another 20, 30 minutes and a little bit of love.\u003C/p>\u003Cp>Man, this thing would be amazing. So I can go in, if I refresh this page, we should probably ditch that, but I can go in and add my pancakes. I can place the order and see it show up inside Directus. Alright. So we just do a quick review.\u003C/p>\u003Cp>What did we achieve? Right. We got the data model done, check. We got a list of the menu items for a restaurant, check. And then we got the place an order for menu items.\u003C/p>\u003Cp>Boom. Knock all 3 of those off. I'm gonna credit that to my wife being here to help me today. That's it for this episode, guys. I hope you've enjoyed this one.\u003C/p>\u003Cp>If you did, make sure you hit the subscribe button on Directus TV because we want you to catch more of this content. See you.\u003C/p>","Hi guys. Welcome back to the next episode of 100 apps, 100 hours, where we rebuild or clone some of your favorite apps or publicly fail shamefully. Shamefully fail trying. Alright, I'm your host Brian Gillespie, developer advocate at Directus. Super excited to have you. I've got a special guest, my lovely wife joining me. I'm not sure if you can see her on my shirt here. This is a running gag between us where she basically gets me clothing with her face on it. So we'll include her in this episode. Today we're all about building a food ordering platform similar to DoorDash, Grubhub, or it seems like one of these platforms pops up every single week. The rules are very simple. We have 60 minutes to plan and build this application, no more, no less. And the second rule, the anti rule is we're gonna use whatever we have at our disposal, whether that's AI, UI frameworks, front end frameworks. We're going to be using Directus on the back end to generate this app really quickly. So let's kind of talk about the food ordering app before we dive in. You may have seen some of these before, we've just got a list of menu options, we want to be able to place those inside a cart and place an order for that cart, have somebody bring that food to us. So that's the basic gist of what we're building. Let's get started. Alright. So let's roll the timer and begin. I'm gonna just pull this to the side and let's pull up DoorDash just to take a look at this, right? So here's kind of what we're after. We've got different restaurants on here and if I click into one of these, like the bun stop shop, I have a menu of items here and we can add these to a particular cart, Choose some options. I don't know that we'll get to things like options. We'll probably just try to keep this very simple to begin with. We'll go ahead and add this to the cart and now I can go and place an order for this and have it sent to my delivery location. So great. That looks good. Let's discuss the functionality that we actually want out of this particular application, right? So I'm going to drag this down. In 60 minutes, I don't wanna bite off more than I can chew here, if we're doing cliches. So let's, you know, we've got the data model for the back end. We'll get that set up. On the front end we will show a list of menu items for restaurants. Allow place an order for menu items, and that's probably pretty good for an hour. I don't know. We'll see how far we get with it. The next thing I always like to do is to sketch out my data model. And one way that you could do this is obviously if you're trying to model another application, right, you could always kind of look at it and think through it. One of the things that I like to do, in this case I'm gonna just search for the DoorDash API because I know that they have other services that connect to this. And it looks like they do have an API that we could potentially use here. We don't wanna drive for DoorDash. Maybe it's this marketplace API, how to guides, menu flows and order flows. Here we go. Retrieve orders from DoorDash, set up a menu pull, set up a menu push, get a DoorDash menu. Okay. Yeah. So I can at least see a little bit of how they've got this structured, right? So I see there's a store that's associated to this. We have a menu. I'm assuming this is like a could be a breakfast menu or a lunch menu or a dinner menu. Maybe we have different menus that way. Then we have categories, I see, and then we have the actual items. Alright. So that gives me a good idea of of how they've solved this problem. And obviously, they're a large company, so they've got a lot of smart people on it. Do I want to take that at face value? Probably not. But for an hour, it seems like a pretty good model. Right? So let's start sketching this out. I'm just gonna make this full screen. We'll add a little box here. We've got, stores or restaurants. I'm not sure which one of those I'm gonna call it yet. Probably stores is easier. Then we have a we have menus. What else do we have underneath that? We've got menu items. Probably like categories. Right? I saw that on there. So we've got different items in the categories. Menu items, and then we're gonna have an orders table with probably some order items as well, orders items. Alright. Don't necessarily have to do this, but, yeah. Each menu belongs to a restaurant. The menu items belong to a category. So there's a relationship there. Just draw these arrows. This is more for me just visually to see how everything maps. Directus, our back end will make creating these relationships and these models very easy. Great. We probably have a restaurant that's attached to the order. We have a menu item that's attached to the order item. And then we've got, an actual person placing the order. Directus is gonna give us that. That will be our users table probably. User, users place orders. Okay. Alright. So as far as the structure of this, we got that. Don't worry about that little guy there. This seems pretty good as far as the structure. Right? So how do we start building something like this? I'm gonna pull up my blank instance of Directus. Directus is going to mirror all the changes that we make here inside the application, all of our collections. Those are going to be tables inside our database. All the fields are going to get represented as columns. So it's a great way to build out this functionality. And for me, I'm very front end oriented, so I'm a very visual person. So instead of writing migrations, this is great for me. I can go in and build this data model visually. And if I need to store it as code I can kick out the schema dot JSON file as well that others who are working on this project could apply. So let's go with stores. I really like that. If we just take a look at DoorDash, one of the things that I was curious about, okay. It looks like we have an ID for the store. They're not using anything like a slug. So it doesn't really matter for SEO purposes. Let me get back here. So we're gonna create this new collection. I can zoom in a bit. We've got stores. We've got our generated UUID. Let's go ahead and and just add these system fields here, just for status, date created. You know, they give us some of that functionality out of the box. So we're gonna have a name for the store. We're gonna keep this really lightweight so we could try to get, push orders into this. So we'll just do a name. Let's do a logo or an image. Let's just call it image for the store. Great. We probably got something like an address, probably. Right? Street address. We have a city. I'm just gonna duplicate this field, which is really nice. Street is a city, state, or region. We're trying to be international friendly here. And then we'll have postal code. Great. Cool. And then I may even just like group these together inside Directus using our detail group interface. We'll just call it address info. And we can even add a nice little map pin. There we go. So we'll group these together. Region and city, we can make those half width. Directus really allows you to customize the forms that your users will see. You know, on the front end here, I'm probably not going to give the people placing orders the access to the Directus Data Studio here. But, I certainly could give the restaurants owners or the store owners access to that to manage their menu, their store information. Alright, so we've got, this looks pretty good for our stores. Let's move on to the next one. Actually, take that back. Let me add a building, like a company, Office building. Building. Business. There we go. Alright. So that looks kind of like a store. Great. Alright. So next we are going to add menus. Alright. We will, I can again, I can add these things whether they are in draft mode or publish. You know, I may want to track when it was created, who it was updated by, etcetera. We'll give a name for the menu. And I'm just going to skirt the categories thing here, and just add these. Maybe we don't. But, yeah, let's do. We'll just go menu items. Right. And for menu items, again, I'll just populate these. They're always easy to remove later. We might want to have a sort field so we can order those items, not not actually place an order for them, but order them in terms of like sorting. What else? Okay. So on our menu items, if we look at DoorDash, we've got we've got a name for the item. We've got, description. Probably a price. Alright. Where's our DoorDash menu? What does an item have? An item has a name, it has a description, is it active, is it alcohol, is it bike friendly? I would argue that a cheeseburger is not bike friendly, but I'm sure this is on the delivery side. And then I see things like price, I see a tax rate, and then I see, like, some extras. For the purposes of this, let's keep it lightweight. We've got a name for this. We've got an image image for the menu item. We'll just create that. Let's have a description. I'm just gonna keep that as straight text. Toggle the sidebar within Arc as well. So you probably don't wanna do a WYSIWYG description here. And there's always risk if you're not sanitizing HTML content that is user submitted. So we'll just use text area. What else do we have? We have a price that we're going to set for this. That can be an input. I I know a lot of ecommerce options store the prices as integers, like incense. I'm just gonna use a decimal here And I could go into field creation mode if I want to, if I wanna control like the precision and things like that, and the scale. So I will just put 2 here. I only want 2 decimal places. We could do a formatted label and I can even add a prefix within Directus, which is really nice here. We'll do auto format, see what that generates for us. Cool. So we've got our menu items, we've got menus. Alright. Let's add a relationship between all of these. So we have a store, it has a menu or potentially menus, and then each menu has menu items. So if I go into menus, the designer or the recovering designer in me is gonna freak out if I do not add at least some type of icon for this. But let's add a relationship and this is gonna be really easy inside Directus. This is gonna be a one to many relationship because we have items on our menu. We have one menu, many items. So I'll go in and we'll call this, items is going to be the name of our field in the menu collection or the menus table and then our related collection is going to be menu items and the foreign key would be the menu. Right? So inside that menu items we're going to create a new field called menu. And I definitely want to show a link back to that item within the data studio. Now if you expand this into the advanced settings when you're creating relationships or just adding fields to the collection, you can see here that menu is not on menu items. That field does not currently exist. But Directus is gonna create that for us, which is really nice. Right? I don't have to mess with sitting down and doing migrations. As you can see, we're gonna build this functionality really quickly. Alright. So we have menus, we have a name, we have menu items. Let's go ahead and save this. If I go into our menu items, now I've got a field for menu, so that's going to show up. What else do we need to add here? Right? We're gonna need an orders table. So we'll say orders, we'll definitely wanna know who placed those orders, what's the status of that order, is it ready or not. And then let's go in and add order items. Again, naming things is very challenging, you know. Should this be called orders underscore items or order items? I like it this way. Totally up to you. Whatever you wanna name this inside your own. Maybe we sort those. So we've got orders, we've got order items. The order, if we just think through this, and of course add a great looking icon for it, the order is gonna be linked to a specific store. Right? We can only place one order from a store. So we'll go in, this is gonna be a mini to one relationship, and we've got this up over here so we can see everything we've got going on. This is gonna be the store. The related collection will be our stores, and Directus is going to create that relationship for us. And again, if I were to just open up Table Plus, so you can get a look at this. What is happening behind the scenes here? Directus is creating these relationships, it's creating my tables, it's creating all my columns in the database and it's also creating those relationships for me inside the underlying SQL. So if I were to rip out Directus one day, you know all my SQL data is still pure everything inside my database. Alright, so we've got an order, we've got a store, we've got a, a customer. Right? We'll call that customer. This is gonna be the Directus users collection. We've got an address for that so we could go in and do street address, city. I can also go back to that other collection and just duplicate these across, which is probably what I should have done. But no worries there. Alright. So we'll add these fields and again, I could group these together using our detail group. We'll call this address info. You know, we may again, we're gonna have a front end where users place their order, but on the back end we could fulfill these orders through the Directus interface as well, through the app. And that again that helps me just move things along faster so that I don't have to build an admin interface. I could just use Directus here to manage all of that data. Alright. Orders and then we have our items, right? So we'll go in this is again, this is gonna be a one to many relationship. There are one order or there's one order that has many items. Because I know yeah. At least at first, or when you're modeling data this way, it can be kind of confusing of which relationships to use. So this is a one to many from the orders table to the order items. I'm gonna call this items as well, and the table is gonna be order items, and then we're gonna use a foreign key of order. I always like to show a link to the item so I can open that up inside Directus and we'll just hit save here. Great. We got a store, we got a customer, we got a street address, we got a status that we can go in and adjust. Alright. This is a draft order, published order, an archived order. That doesn't make a ton of sense. Alright. So let's just nuke those. We'll call this a new order. We'll call it, In Progress or Being Made. I don't know. Again, naming things is part of the one of the hardest things inside development. We'll say ready for pickup for pickup. And what? Delivery? I'm in out for delivery. Delivery. Out for delivery. Out for delivery. Let's just say delivered. Right? I think those are just a quick rundown of what the different states could be for a specific order. Is it new? It's being made? It's ready for pickup? Somebody comes and picks it up, it's out for delivery, and it's delivered. Right. Again, it's not as simple as this. Right? You've got a driver and things like that included, but we're keeping this very simple so we can actually dive in to the functionality. So on the order, we also probably have like a total for the order as well. Right? Order total Order total that would sum up all of the individual line items. This is gonna be an input field. The schema though, we probably want the type to be decimal, just because that's what we have for our menu items. Great. Again, you could store this as synths and you know, do all of that on the front end as well. What do we what else do we have? We've got the order items, we got the order, and then we're gonna add a relationship here to the menu items. So this is gonna be a mini to one relationship because we can only add 1 menu item to one order item. That's great. So we'll call this the menu item. Sounds great. Menu items. That's our related collection. We'll hit save. What do we have next? We probably have a price for this specific item as well. Again, that's gonna be a decimal. I can adjust the number of decimal points. Great. And then one of the other things that I saw on DoorDash here, this is not a an actual order. Let's see if we can find a an order. Right. What does an actual order look like? Receive orders from DoorDash, blah blah blah, notable fields. Let's just look at the UI. So I go in and I add this. I can add special instructions. Right? So let's add a field for special instructions. And we also probably have a quantity. Right? I can go in and adjust the quantity of these. So if I want 3, 4, 5 hash browns, I don't have to add a separate item. So we'll do quantity. That'll be an integer. You can order half of a hash brown. I guess you could. I'm not sure that they would deliver it. So we have quantity, menu item. Let's show the actual order. This little hidden I there tells me that that is not displaying to the person when they pull up this form. But there we have the order, we have the menu item, we have the quantity, we have the price, and we probably have an something like amount or subtotal, item subtotal. Again, this is where it gets tricky as to what you name these things. So the subtotal here would just be the quantity times the price, that's going to equal our item subtotal. Alright. So this feels pretty pretty good, right? That's, kind of what we had. We just totally erased categories for now. We just wanna get to this core functionality. We've got, roughly 30 minutes and some change to evaluate this, right? So first, let's go in. I'm just gonna pull this up side by side, and I'm just gonna steal some of these items. Right? Let's go in and I I don't wanna do McDonald's. I've got my address set to Yankee Stadium here in the Bronx. If you work at Yankee Stadium, I'm sorry. We're gonna deliver some food to you. May not be something that you like. Alright. Let's go with wallet friendly. Jimbo's Jimbo's Hamburger House. Looks good. Let's go in and add a new store over here. So inside Directus, you can see how that form, that data model that I set up, how this actually looks when we are managing this. So again, you're probably for something like DoorDash or an order delivery app, you're probably not going to give the person placing the order, like the end user, access to this. But this interface is so beautiful, I could certainly give the store owners that wanted to sell their stuff on our platform access to this. So this is 252 Saint Ann's Avenue, Bronx, New York. I don't have a postal code, we'll just say 5555. Right. And then I can see I've got a image here. I can just copy that image address and Directus makes this really easy to import images from a URL. Boom. Great. Maybe I even wanna show this image. When you are creating your data models, you've got a ton of options as to how these things display inside the application. So I could go into this image field and I could display a tiny image preview. I can even make it show up as a circle like what they have there. Great. There we go. We've got Jenbo's Hamburger Stand, Hamburger House. Let's go into our menus. Right? So we'll create a menu. Let's call this the lunch menu. We're missing that relationship though. Right? Where did we did we actually create a relationship between menus and stores? We did not. So let's do that quickly before we get too carried away. We will create a many to one relationship on the menus back to the store. Stores, and then I can open this up and in our relationship field, I can add that back as well. So here's our menus. Cool. Great. Alright. So now I've got that relationship. I go into the menu, I can pick the store, there's Jenbos hamburgers. And let's start adding some items from this. Right? We've got our silver dollar pancakes. This looks good. Items are silver dollar pancakes. I can copy that image address and have direct us pull this in. We've got some additional options here. I don't see any like descriptions for those. Not a big deal. The price is 6.50. So we'll just input that. Alright. Next, let's add some other options. We've got a Philly steak bacon wrap with fries deluxe. So we'll just add that here. Looks like I don't have an image for that one, but I do have a description. What is the price for that? It is 12.50. Great. So we'll just input some of this data. Why do I have an image on this? Jenbo, you need to get some images on DoorDash, my man. So we'll just do a Philly steak and cheese. Who knows where we're ripping this from? This looks good. Copy image address. That one's not available. Philly steaks. I guess this is gonna be all sorts of copyright infringement here. This is for entertainment purposes only. Any lawyers watching this, just so we know. Maybe I could get Nat. Nat edits our videos. He's amazing. Nat, maybe you could place a disclaimer on here somewhere. Alright. So we've got a couple of menu items. What else do we have? We have waffles. We have tex mex. Good enough. Alright. So we don't have any orders. We don't have any order items, but at least we have a couple menu items and we have a store. Great. Let's dive into something on the front end. Right? What am I using on the front end? I've got a just a basic Nuxt starter kit. I'm with you guys, so I I like coding in Nuxt and Vue. And then I've just got this simple app. Right? So if we go back to DoorDash, we've got to get some kind of view where we have a list of restaurants. And how do we do this? Right. Nux has a Nux 3 has this use async data composable. So let's just call this stores, that's our key. And then inside this starter kit, I've just got a module that communicates with Directus. I've got a little composable here that auto imports items from our, like the different methods from our SDK and allows me to communicate. So we'll just adjust this. This is going to be our stores. And here, let's just maybe, we're not gonna flex this. But let's just log our data and see what we're getting back. So I could do data. Maybe I wrap this in a pre tag. Bada bing bada boom. I should be able to get this data. We should be reading something from stores, right? I'm not. So the issue here is that I have not set any permissions. Directus comes with rule based access control, which is pretty amazing that I can configure all this via the UI and it applies in real time. But we've got 2 roles by default. The administrator role, which I can apply to users who have full access to the data model, all the different settings, and then we have the public role. So the public role, we want to give access to, probably not So they could probably see the stores, they could see the menus and the menu items. We don't want to give them ability to see any of the orders or create orders. Right? You want to log in for that. So we might as well go ahead and add a new user here as well. So the user role should be able to place orders, they should be able to view orders, with the exception that they can only see their orders. Right? We don't want them to create menu items, but one of the other nice things about the rule based access control in Directus is I could set up custom permissions. Right? If I am a user, I I shouldn't be able to see everybody else's orders, I should just be able to see mine. So I could do something like this, where in this read permission setting for our orders collection, I could go in and control the items that are available. Right? So if I've got a user that created this, has to equal dollar sign underscore current user. So basically this is a little syntactic sugar. This will, fetch the current user ID for you and set up permissions so that if I'm logged in as a specific user, I can only see my orders. That's super nice. Alright. Cool. Let's go back to our front end. Now I refresh. I can actually see some data. This is good. Right. Now let's go in and actually flesh this out. We've got div, maybe we've got a grid of, what, 3 columns. I I love using Tailwind for the styling. Grid calls 3, maybe a gap of 4. Alright. And then we have I'm using the Nuxt UI library just, as a cheat codes here. They have a card component that I can use here. We'll just say ucard. We'll do a v 4. And it looks like, GitHub Copilot is propelling me forward here. We have the store dot ID. What are we gonna drop inside the actual card? We probably have the what do we wanna do here? We wanna add the image up top, and I can see there's like a template, like a header slot and a base slot, but maybe we just stick it all in the same one. I'm also using Nuxt Image in this as well, which is really nice because, I don't have to create any it will automatically optimize the images for me. And Directus will do that via the API as well, but this is kind of nice just because they use the same underlying technology, the same library. So let's do height 48, width 48, object cover, Jinbo. We're not getting the actual image. Right? What's going on here? So if I check our network requests, if I just slide this over to images, we're getting a 403 forbidden on the images as well. Right? So images and assets are a system collection inside Directus. So I've got Directus files, that's where all of our assets get stored, aside from the actual image on disk, we're keeping that library of them. I forgot to enable control for those. So I can go in under system collections, I'll just enable access to those files. Let's go ahead and give this user access to those as well. Where are you? Files. Okay. They've got access. That's great. I probably shouldn't have closed Directus out entirely. But now I could see I've got this, right. And for the width, let's just do with full instead of 48. Okay. Object cover. It's a little pixelated. It doesn't look great. But, next let's go in and add but we're going to add a title for this, right, the store dot name. Let's make this bold. And maybe 3xl. Jinbo's hamburgerhouse. Okay. Alright. Up top here, we can add a h one tag, restaurants or h 2. Or maybe this is a h one. Bold text gray 600. Maybe we add a bit of margin to that. It's a little much, maybe mb 8. And then here for this, I think we've got a container here as well that we might use. I think it just applies some padding. You container. So we get away from Okay. Yeah. Now we're not pressing on the edges of this. Another one of my favorite tools is Tailwind UI. If you build apps and you like speed running things like I do, super handy, right. I could go in and I've got store navigation here. I could just basically lift one of these store navigation components if I wanted to. So let's look for like a header marketing elements. We've got a header here. How do I just get something really simple? This looks pretty good. We'll just copy this. I'm gonna create a new component. I'm gonna call it the header dot view. We'll do a view component. What do they got? They've already got some of this for me. Let's just copy it and see how far we get. I could go into our default layout and maybe I want to add the header here. See what that gets us. Failed to resolve the imports for the icons. They're using a different icon set than I am. I'm gonna delete these out. And I'm not even really super concerned with this navigation either. Let's just make this our cart. We'll change that to a button for now. Type equals button. What do we get? Are we getting anything? Yeah. Cart MX button. Why is it not showing that? Oh, this is probably the mobile menu button. Hidden flex type gray. Justify end. Is it not showing? Okay. There it is. Product login. Let's do something like MD here. That way this is not hidden. Empty. Refresh. Okay. So I got login. Let's just change this to cart. Again, I'm gonna change this quickly to a button because we are going to use it to display our cart at some point. Alright. Do we even need product? Let's just remove that. Forget about it. Okay. So we've got, we got a menu or we've got a restaurant. Right? Now we need to be able to click on this specific restaurant. We go into that restaurant and then we're gonna display a list of all the items from that specific restaurant. Alright. So let's go in and create a new route. We'll call it restaurants or stores in this case. And inside that directory, I'm gonna do something like this where I use, what are those called, just brackets? Yeah. We'll just use a dynamic route here, vcomp ts. And I could copy this async data call here. And let's call this our actual store. So we've got our stores. We're gonna read. We don't wanna read all the stores. We just wanna pick up 1 individual store. And we're gonna do that via the route parameters. So we'll do this route equals use route, which is just a composable that ships with view 3. And here should be route dot ID, and then as the third argument we can pass in, you know, like, hey, I want all these specific fields or not. Again, anytime I'm just like testing this out, I may add something like pre and just log that data just to see what I am working with initially. Okay. So we'll go back to our index page. We wanna make this, clickable. Alright. So maybe we wrap this in Nuxt link. The 2 is gonna be, let's say, dash stores store dot ID. Alright. Let's see where that gets us. Now I can click on this and it goes to the store. Right? You can see that we had a route change. Store, for the let's do something like this. Async data for the the caching, it uses a key. So I could pass the key like this and do route dot ID. Let's see if that gives us what we're after. I refresh. Why am I not getting this? Oh, route dot params.id. Duh. Silly rabbit tricks are for kids. Alright. So here's the data for the individual store. Alright. If I go back, you can see the difference. I'm getting an array of items here because I'm calling the Directus API via read items. When I click on Jenbo's hamburger house, I get just the actual item because we are using read item here. We're picking that up by the param ID. Now, how do I get access to this actual menu item? Right. A couple ways I could do this. I could make another call or one of the many things that I love about Directus is the ability to get all the related fields in a single call. And I'm going to use this wild card syntax here just so we're we could take a look at it. In production, you probably wouldn't want to use this. But if I wanted to, I could just fetch the ID. Right? So it's very GraphQL like and then I can tell it specifically the fields that I want and I can go in and do something like this where I get menus dot star. We can see okay, here's the the different menus. I want to drill down one more layer and I want to get menus dot items dot star. That is a wild card for all of these items and that could give me everything that I need to render this on the page. Right? I could go one further if I'm storing like metadata, like alt tags or title text or something on the image. Itself, I could drill into that further. Really all I need to render an image is just the ID, but great. So now how do we set this up? Let's go in and we'll add a menu. Let's add an h one. This is gonna be the store dot name. Class 3xl. Font bold. Maybe we don't make this bold. Let's see what we got. We got Gino's hamburger's house. Maybe we dress this up a little bit. What does it look like inside door dash? Got like this nice header image up here at the top. We don't really have one of those, but I can at least show the, logo for the store. Right? Let's make it look nice. Store dot image. Is that what we called it? Class let's make it square. 48. Let's do 24. We'll make it smaller. Right. With 24, object cover rounded full, this should give us the image there. We could probably wrap this in, div. Just flex that. Class equals flex to a gap of 2. And then within this, we might wrap that in a div as well. Okay. Cool enough. We've got that. Now let's add these actual menu items. Right? So I've got this array of menu items. Let's just take a peek at how we're doing on time. Roughly a little less than 20 minutes. Are we gonna get this done or not? We shall see. Right? So I'll go in. Let's just do a grid of menu items. Great. Got a gap. Go co Copilot go. Right? We're use that same ucard component. V for menu and store menu. I really like GitHub Copilot. It's great for like really simple auto completion things like this. One of the things that I can tell you is make sure whatever you do, you verify all of this before you actually use it. Right? Because here I'm getting okay. This is not actually working at all. So v 4 menu and store menus, we really just want the first item. Right? We're gonna do the items in store menus 1, the first item in there. That's gonna be item dot ID. We don't really need the store. Right. This is kinda weird. This is where GitHub Copilot gets you into, a lot of trouble if you just blindly do whatever it's it's saying here. And this is still not coming up right. Item in store dot menus dot 1. This would be the actual menus dot items. There we go. Okay. So now we could see we've got our silver dollar pancakes, we've got our Philly steak and cheese. Let's make this smaller. And then at the bottom, we're going to add our price. Don't forget the item dot description. Then we also need a price. Alright. So item dot price. Let's just see what that looks like. We can add a dollar sign. We've got our silver dollar pancakes. Cool. Maybe we wanna give a little bit of gap between all these. This is not gonna be a Nuxt link either, right. We want this to be a button type equals button. We'll add some functionality into this in a moment. Class equals text left. Okay. Maybe we give it a flex call gap 2, Give a little bit of spacing in there. And make the width full. Well that, yeah, there we go. That gets us what we want. Let's make the gap larger. Okay. Cool. So we got some menu items. Right? We want to click on these menu items and add them into a cart. So what are we gonna do, how are we gonna manage this cart? Because it could be we want to maintain this cart across like page load and also across different restaurants or different pages as well. So for now, I think I'm just going to simply use a composable for this. I don't have Pina installed, which is a really great state library for Vue. You know, Nuxt has a built in used state composable that you could potentially use as well. But I like the idea of just adding a composable to manage this. So I'm gonna add a new folder here in the root directory. I'm gonna call it composable Composables. Nuxt will actually auto import these. And let's call it use cart dot ts. Alright. We're going to export a default function, use cart. And up here at the top, let's just take a look at our cart structure. Right. What do we have? What do we want to send? So we've got the orders. If we take a look at our data model, right, we have the store, we have the customer, we have that's gonna be our individual user. So we've got to log in as a user. We got our items. We got a total for that. Okay. So let's go in and add a list of items. That's gonna be an array of items. Right. What else are we going to have inside this composable? Right. We've got our items. We're pulling this outside of the function definition because we want to store this. If we call this use cart in multiple places, we still wanna access the same items. Alright. So this is gonna return the items for that cart. We're gonna add those cart the order total. Alright. So we have the order total that's going to be computed. That would be a return items dot for dues dot item dot price. That's perfect. Thank you, Github. Got an order total. And then we've probably got a function for, oh, let's go back up, async function, place, yeah. Let's add to cart. We're just gonna push an item to the cart. We gotta remove from cart. Look at GitHub Copilot. Again, like, please take this with a grain of salt, whether this is actually wise or not. Index of item, items value dot splice. Index self item. Okay. And then if we've got, and these actually probably don't need to be async functions. And then we probably got an async function for submit order. Alright. Submit order. Alright. This is gonna be constant data equals I don't know if that's gonna be data or not. We'll do await. We'll do use directus. Then we are going to create an item in the orders table or orders collection, the total for that order total, ordertotal will be the order total dot value. Then we have our items, which are going to be, should just be items. Alright. So we could do something like, nope. We're not gonna do that. We are going to do unref items since that is unrefed. We've got the store info and this customer field, inside direct us, I can save the current user on create. So let's do that. So the store is going to be where are we gonna get the store from? Maybe we're gonna pass that in the function. Store ID, that's a string. Store ID. Alright. Is this cool? We're gonna return these other functions from our composable. Let's see how we do. Alright. So now if we go back to our app, we've got the hamburger house. Let's just remove this, comment that out. Alright. So we got our menu, We want to push these items into a cart. Where are we at time wise? We got 10 minutes left. Are we gonna actually get these into a cart or not? So here inside this specific, page we might do what we are going to return. Let's take a look at our composable. Right. We've got our order items. We've got our items in the cart. Let's call the we're gonna get items. Let's call these cart items. Add to cart. Remove from cart equals used cart. K. And then can we hey. Let's just drop in our cart items here just to see what that looks like. Cart items. It's It's not showing anything for our card items. Are we getting an error? Let's actually take a look. So we'll go to ID. Cart items equals undefined. Oh, it's because I'm getting items. There we go. Alright. So we're showing just an array. Let's just do this. I'm just gonna give this some padding. BG gray, 50. Let's make it 100. This is our, p tag cart items. Okay. Alright. So now what we want to do on each one of these, right, if we click an add to cart button, which we we don't necessarily have. Where is that? Right? Why do we not have a button for add to cart? Maybe we don't even put that. That shouldn't even be in here basically. So let's move this up. We'll just take the class. We'll apply that to the card itself. Why is it not doing what we want? Gapspacey2. Why are you not doing what I want you to do? Alright. I'm not gonna stress over it. We'll just wrap it in a div because this will be the div task adventure. I know all the non tailwind CSS guys are probably freaking out at this moment, but okay. We'll use this u button component provided by Nuxt UI and we're going to click add to cart. We're going to add that item to the cart. Yeah. Close enough. Right. So if I click add to cart, we can see we've got our cart item here. It's not necessarily what I want. Right? The data structure is not entirely what I want. So let's actually just define it here. The item ID, we want this to be our order items. Alright. So we go into order items, We got the menu item. Let's do that. Menu item. Okay. Special instructions. No onions, please. Yeah. We'll just automatically populate that. The quantity, let's set that to 1. And then the item subtotal, we'll just say item price. Alright. So now if I refresh, does that give us kind of the structure that we want? It's still populating the entire menu item. We just want the menu item item.id. That should give us what we need. Okay. Does that match the right structure? Great. Where we at on time? We got, 5 minutes or so. Let's actually get this order together. Right? Alright. So inside our header, maybe we could display our cart items there. I really wanted to actually show the submitting to Directus. So knowing that I am short on time, right, we could go in and add these items. Let's just try submitting it right now and see. Alright. So we got our cart items. Let's go in. We've got new button, place order, and we'll call this submit order. Are we passing we're passing that store ID as well. Alright. So this isn't probably the best particular type of design, but it will get the job done. Let's do a large button. At click is submit order and then we've got the store dot ID that we're passing to this. And then let's go in and just like what does our submit order look like? This should be probably like try catch. So we could catch any type of errors. Response. If we catch an error, we want to return the response. Great. Submit order. Oh, that looks a little rough. Let's move that over here. Oh, I put I did I totally put that in the wrong spot anyway. Submit order and let's just hide this if the user is not logged in as well. Right? We don't want them to be able to place an order if you are not logged in. So this composable that I have, I want to say, has a or like this module that I set up has some of this already built in. So this will be v Where's the directus auth? Directus auth, user. So we'll do something like this where we get constant user equals use direct us off if there is a user. Okay. Alright. And if there's not a user, let's do a u button. V of no user, we are going to I think I could do this with it, where we have login to place an order. Login to order. And we will do what is it? 2 auth, I don't even need that. We do to auth/auth/login. Login to order. Okay. We've got our user. I didn't set up a user, but let's create one now. This will be our user. Test usertest@example.com. Let's give them a real secure password. We'll do password here and then I am going to select the role of user. Great. Save it. Now I should actually be able to log in. So test at example dot com, Password of that login. Tried to log in to the wrong spot. Where's the redirect for this? Use direct us off. Is this going to be in my Nuxt config? We want to redirect to auth login, not portal. We want to redirect to the index page. Oh, we're gonna we're gonna be cutting this one really really close. Come on. Alright. So we go back. Do we have we've got our menus. We don't have any cart orders. It looks like I'm still am I still logged in? Do we have the cookie? We've got okay. So we've got a session token here, so it's still showing me as logged in. If we hit add menu items, I hit place order, what happens is we can see the order posted. If we go inside Directus, do we have that specific order? Yes, we do. So we've got here's the store information, here's the order, we've got the item subtotal. We don't have the prices not being populated. So I didn't item subtotal should be item dot price. Why is that not populating correctly? I didn't add the price either. But item dot price. Cool. I'm calling this a win. So we've got what have we done? We've set up our data model within an hour. We've got all the items coming through. I could go in and clean this up, certainly, and that would be my next steps. But we've got a really simple UI here on the front end that with probably another 20, 30 minutes and a little bit of love. Man, this thing would be amazing. So I can go in, if I refresh this page, we should probably ditch that, but I can go in and add my pancakes. I can place the order and see it show up inside Directus. Alright. So we just do a quick review. What did we achieve? Right. We got the data model done, check. We got a list of the menu items for a restaurant, check. And then we got the place an order for menu items. Boom. Knock all 3 of those off. I'm gonna credit that to my wife being here to help me today. That's it for this episode, guys. I hope you've enjoyed this one. If you did, make sure you hit the subscribe button on Directus TV because we want you to catch more of this content. See you.","published",[139],{"people_id":140},{"id":141,"first_name":142,"last_name":143,"avatar":144,"bio":145,"links":146},"791e1503-1d88-463d-9347-0b9192933576","Bryant","Gillespie","9013afc8-e8d7-4182-9b18-44db08117bb9","Developer Advocate at Directus",[147,149],{"url":131,"service":148},"website",{"service":150,"url":151},"github","https://github.com/bryantgillespie",[],{"id":154,"number":155,"year":156,"episodes":157,"show":168},"56dda5ff-2c3a-41ce-ae3a-580d6101026b",1,"2023",[158,159,160,161,162,163,164,165,122,166,167],"cb4e067f-9507-4e18-ab9a-435565f9e653","8434838a-8e4f-489a-8da2-fbf10de5de6a","c997b25e-400c-4350-bba4-f63853d844f7","1109be0d-8ab5-479b-a052-8ad30d9ffb1c","dcb952e0-7d13-43ea-9b1c-f2ca63efd07d","0aae287d-3916-4d91-9310-25828998e562","8fed0eee-43f6-4767-8b77-79da7a059821","a311b57d-34e7-4073-9cf3-6a8c6c0f8b85","30c48566-52cd-4728-a633-ca9675acc959","c067c012-5fe1-4d70-8946-8bfc8e1b22c0",{"title":169,"tile":170},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"id":172,"slug":173,"season":174,"vimeo_id":175,"description":176,"tile":177,"length":178,"resources":8,"people":8,"episode_number":155,"published":179,"title":180,"video_transcript_html":181,"video_transcript_text":182,"content":8,"seo":183,"status":137,"episode_people":184,"recommendations":186},"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","crm","14fda5f2-95de-4dbe-a4e2-3fd956c21c19","936325383","In this intense one-hour challenge, watch as Bryant incredibly builds a full-featured custom CRM from the ground up using Directus. He builds contacts, organizations, deal pipelines, activities, and more.","f6b880a0-5cd2-45ad-beff-3117f0a78581",56,"2024-04-19","Mission: Customer Relationship Manager","\u003Cp>Speaker 0: Hi. Welcome back to another episode of 100 apps. 100 hours where we build some of your favorite apps or try to rebuild some of your favorite apps in 1 hour or less, or get publicly humiliated trying. Alright. Super excited to be back for another season.\u003C/p>\u003Cp>If you are new to 100 apps 100 hours, there are 2 basic rules. Number 1, you have 60 minutes to build and plan and build, no more, no less. So when that clock strikes 0, that's it. And then the second rule is there are no other rules. Use whatever you have at your disposal to complete the functionality.\u003C/p>\u003Cp>That's it. Let's dive into this episode. So today, I've got a custom CRM. Tools like Salesforce, Pipedrive, HubSpot, they really need no introduction. Everybody needs a CRM.\u003C/p>\u003Cp>A lot of them, depending on your purposes, may be way too much for what you need or they may not be specific enough for your industry. So we are going to build our own custom CRM in 1 hour or less. Let's do it baby. Alright. So, let's start the clock and away we go.\u003C/p>\u003Cp>So, when we plan our CRM, what do we need out of a CRM? Right? What kind of functionality do we want to see inside our CRM? So basically, we want to manage all of our contacts. We want to manage all the different organizations those contacts belong to.\u003C/p>\u003Cp>What else? We wanna manage our sales pipeline. Manage sales pipeline. That's gonna be deals or activities. We want to be able to track activities and follow-up.\u003C/p>\u003Cp>So this seems like a a pretty good set of functionality for a basic CRM. I'm sure we might embellish this a little bit depending on how far we get, but let's dive into actually fleshing out what the data model just might look like for something like this. So we'll drag a nice square up here. We're gonna have contacts. We could just call those people.\u003C/p>\u003Cp>I'm a big fan of that. We're gonna have organizations. There's definitely gonna be a relationship between those 2, but I I feel like this could be a many to many relationship because one contact could belong to multiple organizations, and and some CRMs make that a little more difficult to do than others. What else do we have? We're gonna have deals or opportunities.\u003C/p>\u003Cp>Deals is kind of a a standard nomenclature. We're gonna have activities that are attached to what, those are attached to organizations, they're attached to contacts, they're probably also gonna be attached to deals. What else are we going to have? We're probably going to have some sales reps, those are going to be our users inside our accounts. This looks pretty good for a a base set of functionality.\u003C/p>\u003Cp>Let's dive in and actually start building something. So, I'm just gonna pull up my Directus instance, that's what we're using on the back end. Totally blank instance, we'll zoom in on this, and let's start building. Right? Let's create our first collection.\u003C/p>\u003Cp>Let's start with contacts. So we're gonna give this a contacts as the name. For the primary key, let's use generated UUID. What kind of fields do we wanna add for our contacts? Right?\u003C/p>\u003Cp>When we think of contacts, we're gonna need name, email, but we may also wanna track when this was updated, when it was created. Directus makes that super easy. And one tip that you may not have realized if you've used Directus before, is I can go in and change these to be whatever I want. So if I wanted this to be created at and created by, I could go in and update those, change them to be whatever I want. Updated at, updated by.\u003C/p>\u003Cp>That's the the nomenclature that you're used to. Do we actually need a sort for our contacts? I don't think. Let's just go ahead and save this. Alright.\u003C/p>\u003Cp>So for our contacts, we're gonna add a first name. Great. We'll make that a string. Let's do half width for that. I can go in and click the three little dots here and just quickly duplicate this field to save myself some time.\u003C/p>\u003Cp>And, of course, as I am plotting away on this, Directus underneath is mirroring all these changes to my database schema. So we've got our first name, we've got our last name, we're gonna need a email or an email. Great. Let's require this value. And, you know, I could even go as far as, like, making this unique if I I wanted to have some type of unique identifier to match these up with.\u003C/p>\u003Cp>Alright. As far as an interface, you know, I could get fancy with this and we could add a little email icon like an at symbol. That's great. I think everything else is good. You know, one of the other nice things that's built into Directus is I can go in and add my own custom reg x validation.\u003C/p>\u003Cp>So, you know, I can put in some type of reg x that matches email addresses. I don't have one of those handy here, but we'll go ahead and keep marching along here. So we got first name, we got last name, we got email. We'll probably have a phone number at some point, but let's let's go for, like, a job title, probably. I'm trying to think of all the standard fields.\u003C/p>\u003Cp>Maybe we have some notes. We could set that to be a text area field. I don't really need formatting for those. Great. Job title.\u003C/p>\u003Cp>And maybe honestly, let let's strip this out because I'm I'm thinking about that many to many relationship with organizations, and we may have, different titles at different organizations. So I'll show you how we can handle that coming up. Alright. So we got first name, we got email. We could go ahead and add phone number as a string.\u003C/p>\u003Cp>Phone or phone number. Let's just keep it short. We'll say phone. Let's give it a phone icon. Great.\u003C/p>\u003Cp>K. Phone, email. We'll put email above phone. Cool. Looks nice.\u003C/p>\u003Cp>Alright. Let's just take a look. Right? We've got first name. Go ahead.\u003C/p>\u003Cp>Oh, I was gonna add my wife here, but she's gonna get mad at me if I misspell it. Right? Ashley atexample.com. I had a phone number, 555-5555. Here's some nice notes for our contact.\u003C/p>\u003Cp>These are looking great. Right? We've got our contact in here. I could potentially sort and filter this a a hundred different ways, maybe change the layout. But let's dive into the next collection that we wanna set up, our organizations.\u003C/p>\u003Cp>So we'll have organizations, again, for the primary key, I'm gonna use ID and just use generated UUID as the type. And, again, I could change created at I could change this structure for these system fields to be whatever I want. Created by, date updated, that's gonna be updated at, if I can actually spell. And we use this one as well and call it updated by. Great.\u003C/p>\u003Cp>Okay. So now we've got an organization. For our organization, what are we gonna have? We're gonna have a name of the organization. Right?\u003C/p>\u003Cp>Probably, some addresses for that organization. Right? We could have multiple. So that's where we might reach into an another bag of just a different table. What else are we gonna have for an organization?\u003C/p>\u003Cp>You know, let's just do something like country in case we want to, even, like, automatically route new organizations to a specific sales rep. Potentially, we've got a name. What are we gonna have? We're gonna have a website. You know, we may have a a logo if we wanna track that for a company.\u003C/p>\u003Cp>So let's do an image file. Let's call it logo. You know, you might even add things like brand color, things like that if if you're really in tracking that. And then we'll just add another section for notes. This is gonna be a text area field with the type text, and we'll hit save.\u003C/p>\u003Cp>So we've got our organizations. We've got our contacts. Let's make a link between the 2. Right? So how do we go about that?\u003C/p>\u003Cp>Depends on how you want the data to actually be structured. Right? In an application, depending on the setup, maybe a user can only belong to one specific organization. But often inside of CRM and maybe I am working with the local little league. I am a member of the board there, and then I'm also a developer advocate here at Directus or, you know, a a founder at Better Side Shop.\u003C/p>\u003Cp>So a lot of different relationships that is gonna be modeled with the mini to mini relationship inside Directus. Here's how we set that up. It's gonna be pretty easy. We'll just go to create field. We'll look for a many to many relationship.\u003C/p>\u003Cp>And because we're on organizations, our key here is gonna be contact for the or contacts, I should say. Our related collection is gonna be contacts and then we'll just go through and paint by numbers here. Do we want to show these in a list or a table? I'm good either way. We definitely want to show a link to that item so we can get to that specific contact, but I'm gonna pop open this advanced field creation mode just to show you that you do have control over the naming of the junction collection and the individual fields within that.\u003C/p>\u003Cp>So, the default setup here is just going to take this table name and this table name or this collection name and this collection name and marry the 2 together and we end up with organizations underscore contacts. Works for me. We'll leave that autofill set up. And then on the reverse, I'm gonna add that many to many field to the contacts as well. We're gonna keep that as the organizations.\u003C/p>\u003Cp>Alright? In this case, we may have a sort field. So we wanna control the the sorting for contacts, like who's 1st, 2nd, 3rd, that could be helpful in, like, a primary contact situation. And then we have some of our relational triggers. Right?\u003C/p>\u003Cp>On deselect of organizations' contacts, what do we want to do here? Yeah. Maybe I want to delete that association. So I'll set these to cascade. Great.\u003C/p>\u003Cp>So now that back out, we can see we've got our contacts field here. If we go into, yep, looks like we've got, an extra contact table created. I might have typed something wrong. But, so we can see contacts there in the organizations. And then on our contact, I should see organizations here.\u003C/p>\u003Cp>Let's just add an organization. Right? So let's say my wife worked at Tesla. That's in the United States. And, you know, we could add the website, logo, notes.\u003C/p>\u003Cp>I could choose existing contacts. But I'm just going to go ahead and say 1. Right? So now, I could see I've got that organization here, but it's showing an ID, which is not super helpful. Right?\u003C/p>\u003Cp>So I can go into the organization itself and control the display template where I have a name. I could even potentially add the logo to that if I wanted. So now if we check it again, we can see, okay, here's the organizations that they're a part of. Right? Now, you also have the ability to manage the data inside that junction collection.\u003C/p>\u003Cp>Right? So if I go in and and like I said, I may have a different job title at all these different organizations. So I could go in and add a new field here inside this junction collection for job title. Great. Alright.\u003C/p>\u003Cp>So, now if we go into Tesla, we can see the job title is down here at the bottom, but Directus also gives me an easy way to control where that displays. So if I go to our many to many relationship inside our contacts, I can go to maybe we wanna add a sort field for this just to make sure. And then if I go to the interface, I can control where my junction fields are located. So I can put this at the top, which should be great. Okay.\u003C/p>\u003Cp>So now if we just check that out one more time. Right. Now I could see director of DirectUs. So now I could give a specific job title to this specific person within that organization. I could go in and add a new organization as well, like, hey, the little league.\u003C/p>\u003Cp>We can say a board member. Right? Little League Baseball is the name of the organization. Great. Okay.\u003C/p>\u003Cp>So now there my wife is a part of 2 organizations, different job titles for each. Right? Alright. How are we looking on time? We're looking great.\u003C/p>\u003Cp>Let's move on to our next collection that we want to set up. That's gonna be what? Our deals, our activities, what do we want to set up next? Let's go for deals or opportunities. Deals is probably the standard naming for this.\u003C/p>\u003Cp>So that's what we'll stick with. Could be opportunities. Could be something else. That's fine. We'll do created at.\u003C/p>\u003Cp>Just to keep the same structure, we'll do created by. And by adding these, whenever this record is created, it's going to populate a timestamp and a user. The same thing for Updated, whenever it gets updated or who updates it, it's gonna populate that info for us. Updated by. Great.\u003C/p>\u003Cp>Okay. So our deals what we're gonna have for our deal? We'll probably have a name or a title of the deal. Deal name sounds great. It could be good to prefix some of these sometimes if you're dealing with a lot of nested relational data.\u003C/p>\u003Cp>So you get used to seeing name in there a 100 times. Could be confusing whether that's the actual deal name or the organization name or the contact name. So we'll just save the deal name. What else are we gonna have on this deal? Right?\u003C/p>\u003Cp>We'll probably have a average dollar amount or a potential dollar amount. Let's set that to be, decimal. Deal value. We can add a nice little dollar sign to the icon to make this look nice and pretty. And then we are going to have some related fields.\u003C/p>\u003Cp>But before we do that, let's just add, like, some deal notes or something like that. Sounds great. Okay. So we got a deal name, we got a deal value, you know, maybe these are side by side, not a big deal. Let's go in, and now we want to add a relationship to the organization and probably to our primary contact for this deal.\u003C/p>\u003Cp>So those are both gonna be many to one relationships. And we'll set a key of organization here. So the related collection, we'll set that to be organizations. Great. And I could control the display template here.\u003C/p>\u003Cp>We'll just use the name and we hit save. So now we've got an organization that we're gonna tie to the deal. I'll make that half width. And then we also want to add a primary contact. So we'll just say primary contact.\u003C/p>\u003Cp>The related collection here is going to be the Contacts collection. And for the Display template, let's use first name, last name, and let's use this format, which I think is typically how it displays inside an email client where you have the email address inside these less than or greater than SQL or symbols. So let's save that. We've got our primary contact. We've got our notes.\u003C/p>\u003Cp>We've got our deal. What do we need next? Right? We need to track where that deal is at. So we could use, like, a a status field as, like, a drop down for that potentially.\u003C/p>\u003Cp>Right? What's the status of this particular deal? Blah blah blah. Or we could do something else where we have a relationship to a deal stage or our individual pipeline. Right?\u003C/p>\u003Cp>Because then we may want to, potentially have separate pipelines or we we wanna give more control back to our users, so the the sales reps or the sales manager to control that actual pipeline without getting into the admin section. So how can we do that? Right? Looks like I've got a little extra couple of fields that I've been creating here inadvertently. Alright, so now let's add a new collection.\u003C/p>\u003Cp>Let's just call it Deal Stages. We want to manage what are the stages of a particular deal. As far as the optional fields, maybe I don't really not super concerned with these, deal stages. We're gonna call this the name of this particular stage. Maybe we wanna give it a color so we can add some color to it.\u003C/p>\u003Cp>And, you know, I guess we could even give it an icon if we wanted to. We could play around with that and see what that looks like. Deal stages. Let's make both of those half width. And, now, let's just go through and map some of these particular stages.\u003C/p>\u003Cp>Right? And I and I, you know, I've got this s on here. One of the other things that you could do inside Directus, you can't control the translations for all of these. So even if you're working in English, on the the back end, you may want to use prefix tables to keep things nice and organized as a developer. This is a great way to control what displays via the interface to your actual users within the application.\u003C/p>\u003Cp>So I could just call this deal stages or pipeline stages, whatever makes a lot of sense here, and hit save. Alright. So it still shows me deals_stages here, but when I start looking inside the actual app, there we go, we can see our pipeline stages. So, let's create our first one. Right?\u003C/p>\u003Cp>Let's just say, New, this is inbound. Do we have a symbol? We do have a symbol for this. Right? So let's say red.\u003C/p>\u003Cp>This is we need to take action on this. What's next? Right? Qualified, maybe? Contacted?\u003C/p>\u003Cp>Or, let's say assigned. Right? We forgot to assign those to a sales rep. Assign assignment. That's a nice looking icon for that.\u003C/p>\u003Cp>Then we'll say qualified. You know, if we're doing software sales, like, a check mark. Okay. What else? Demo.\u003C/p>\u003Cp>Let's save green. Oh, let's make this orange. Do we have like a demo? Maybe a TV? What have we got?\u003C/p>\u003Cp>Yeah, there we go. That works for a demo as far as the icons. And then we are going to set, like, a last stage, like, proposal. Great. Let's just go gray.\u003C/p>\u003Cp>Cool. Document. Awesome. Alright. So I can control the way that these are displayed through my actual settings, here.\u003C/p>\u003Cp>So I can go into each specific one. And on the display tab, let's display a colored dot for the color. And then for the icon, we're just gonna display the actual icon. Alright. Great.\u003C/p>\u003Cp>If we take a look now, I can see what these actually look like. Cool. And then for my display template for the deal stages, I may even go in and just mirror that same structure where I have a color, then I have an icon with a name. Okay. So, now, what we've done, we've effectively given control over these, and we probably actually need a sort on these as well, so so we can control that value.\u003C/p>\u003Cp>So, let's go in and quickly add a sort. That's going to be an integer value. And because this is coming after the fact, after we've created this actual table, I'm just gonna scroll down a little bit, and I will have a sort field here. So we'll choose sort just to make sure we manage that. And now I should be able to drag and drop these, in whatever order that I like.\u003C/p>\u003Cp>Again, we're giving control back to the users of our custom CRM. We're definitely gonna prevent them from getting inside the admin so they don't mess with the data model. But here, we give them the ability to control what those pipeline stages are gonna be. So now we need to add those to our actual deals. Right?\u003C/p>\u003Cp>So we're gonna go into our deals. We're going to create a many to one relationship. And, you know, this is going to be the deal stage or just stage. And we're gonna use deals underscore stages as the related collection. We can open this up and and just see what's going on.\u003C/p>\u003Cp>I I don't really need to create that inverse relationship. I could. Doesn't make a ton of sense, though. I'm not sure. Alright.\u003C/p>\u003Cp>Display related values, validation. Let's keep it very simple, and then we're gonna use our deal stage. Alright. So now if I go into our deals Why is this oh, it goes in the machine. I don't know what's going on on this particular example, but it keeps creating additional fields that we don't need.\u003C/p>\u003Cp>Alright. So let's create a new deal. Alright. This is gonna be a new deal. We can see, hey, there's our stage.\u003C/p>\u003Cp>That looks great. The deal name is 100 apps, 100 hours contract. It's worth $1,000,000. So we'll add that. How many zeros do we have there?\u003C/p>\u003Cp>1, 2, 3, 4, 5, 6. Alright. Let's make this for the Little League team. Great. My wife is the primary contact.\u003C/p>\u003Cp>Here's some notes on this deal. Great. Did we save it? Numeric value and deals is out of range. Uh-oh.\u003C/p>\u003Cp>Wouldn't be a 100 apps, a 100 hours without, some type of issue. Right? So let's take a look at this. The deal value, what did I set? Precision and scale.\u003C/p>\u003Cp>Maybe we bumped this up way up. Probably could have gone with integers for this as well. Do I have a minimum and a maximum? I I don't have that set. So not entirely sure what's going on.\u003C/p>\u003Cp>Let's just try this again. 100 apps, 100 hours contract, 1 100,000, 1,000,000. Try it again. Save. Okay.\u003C/p>\u003Cp>Alright. So now, we've got this particular deal set up. This is kind of an underwhelming view. Right? And, again, I might control how these things display.\u003C/p>\u003Cp>So if I just go into our deals, we go to the deal stage. If I wanna control how it displays, I can go in and set this. So let's do the color, do the icon, do the name, just because, I'm a I'm a very visual person, and I suspect, a lot of your end users may be as well. We still don't see the organization there. So, again, we'll go back.\u003C/p>\u003Cp>We'll adjust our organization. We wanna display related values. We want to show the name of that specific organization. And let's get even fancier and maybe we wanna show that logo. I'm gonna click on this and then you'll see this one that has a little magic beside it that's a thumbnail.\u003C/p>\u003Cp>That's just a shortcut for, like, a nice thumbnail sized image instead of loading the actual image size. So I donit have a logo for this company, but I could quickly find 1. Alright. So, we'll just copy this image address, go back to our instance, let's load up that specific company, they who shall not be named, Hit import. Oh, that's a data URL.\u003C/p>\u003Cp>Let's find an actual URL that we can just copy. Great. Let's try this again. We can import via URL. Cool.\u003C/p>\u003Cp>And now when I'm looking at my deals, I can see the logo of that organization. That makes it really handy, when I'm working on a deal just to have that extra extra visual reinforcement. Now, when I'm looking at deals, I may want to set up like a traditional pipeline type of view. So I could do that really easily just by switching the layout here inside Directus. So we'll change this to a Kanban layout.\u003C/p>\u003Cp>Let's group by the deal stage and the group title is gonna be the name. So I can see new assigned qualified demo. We have that Kanban view that we're used to. As far as the text, what do we wanna look at? We want to control, let's say, when this was created.\u003C/p>\u003Cp>I'm not really concerned about that. The tags, what do we want to set that to be? Do we want a card image? Honestly, this looks okay. Do we need an actual user on here?\u003C/p>\u003Cp>Probably not. Cool. Alright. We'll just keep it as is and let's let's start fleshing this out a little more as well. Right?\u003C/p>\u003Cp>So within a deal, when I'm working this deal, we're going to have activities assigned to this as it moves across the pipeline. And, for that, let's just go in and add a new table, a new collection, we're going to call it activities. Activities, I think that's the correct spelling. We use generated UID, we'll do created at, createdby, create up, updatedat, updated by. Great.\u003C/p>\u003Cp>And, in this case, maybe we do give a status for this particular activity. Right. Status, great. Let's give a name of the activity, probably a type of activity. Makes sense.\u003C/p>\u003Cp>We use a drop down for that. It seems pretty straightforward or, you know, maybe they're not gonna need to change the activity types on a a very frequent basis. So we'll call it the activity type, and let's add a couple choices to this. So this is a relatively new addition to Directus within the drop down, the ability to have an icon and a color. So let's say text is a phone call, value is a phone.\u003C/p>\u003Cp>One of the other cool things if you want to translate this value, you could use this phone t, or this dollar sign t for a translatable string. And then anytime you have users who are using the app within a different language, inside the settings, you can control all those custom translations. We'll take a look at that in a moment. Let's call this what? Phone call.\u003C/p>\u003Cp>The value could just be phone call, depending on how I wanna store this. Right? It may have an underscore within it as well. It could just be the same thing. Either way.\u003C/p>\u003Cp>Look for that phone. Great. And I could even add a color for that if needed. Maybe I just wanna keep these all the same. Phone call, we'll call it a meeting.\u003C/p>\u003Cp>And for this, do we have a meeting icon? Right? We calendar looks nice. Maybe we want to track demos separate from meetings. Alright.\u003C/p>\u003Cp>So we drag a demo. That was the TV icon that we had. Great. What else are we gonna need? Like, an email?\u003C/p>\u003Cp>Not sure I would Yeah. Maybe, like, a follow-up email. We wanna reschedule that. And then we'll do an email. Great.\u003C/p>\u003Cp>Demo, phone call. I think I'm gonna set this to be underscore value. Cool. Alright. Looking good.\u003C/p>\u003Cp>Activity type. Okay. What else are we going to need for this? We need a due date for this activity and the status, we can use as as is. It's published draft, archived.\u003C/p>\u003Cp>What do I really care about this activity? It probably isn't completed or not. Right? So maybe we scrap status, and maybe we just go for a toggle instead. Right?\u003C/p>\u003Cp>Hey. Is this completed? The default value, great. For our label, maybe we change it to completed and give it a color of color on. Color off is red, just to show that it has not been completed.\u003C/p>\u003Cp>Great. And let's just clean up this form a little bit, making sure everything looks nice. Okay. We are going to add a date for this, and let's set a specific date and time that this thing occurred. Call it due date, end date.\u003C/p>\u003Cp>Due date seems reasonable, but when do we actually need to complete this specific task? Okay. So now we've got an activity. We want to, link this to our actual deal so we can track those. Right?\u003C/p>\u003Cp>So, what I'm gonna do in this case is create a mini to one relationship because this activity could only belong to a single deal. Right? We're gonna use the key of deal. The related collection, we'll set that to deals. We'll show the name of that deal.\u003C/p>\u003Cp>Maybe we show the actual organization as well so I can actually dig into the related collections and and show values from that? Excuse me one second. Sorry about that. Cool. Alright.\u003C/p>\u003Cp>So now, what I forgot to do is create that inverse relationship. You can actually set that up via direct us when you're creating that relationship. But now I can also just go into our deals, not deal. We'll go into our deals, and now we're gonna create a one to many relationship back to those activities. So we'll call this key of activities.\u003C/p>\u003Cp>We've got activities. The foreign key will be deal. That already exists. Maybe we wanna show these in a table. We'll choose the columns, due date, Name, Due Date, Activity Type.\u003C/p>\u003Cp>Seems pretty savvy. And I can even filter these, right? So if I wanted to see just activities where they were not completed. We can enable search and filtering and show a link to these. We'll take a look at what all these look like in just a moment.\u003C/p>\u003Cp>But we've forgotten one important step through this whole process is, hey. We need somebody to assign these deals to. Right? So, let's add a many to one relationship. We'll call that the deal owner or, you know, you potentially say who this is assigned to.\u003C/p>\u003Cp>Like, the deal owners, again, kind of standard naming in these scenarios. And for the related collection here, we're gonna use directus underscore users. So these are gonna be actual users of the application that we're assigning this to. Invalid payload collections can't start with direct us users. Oh, deal owner.\u003C/p>\u003Cp>Let's go to our related collection, and let's get direct us underscore users. And, in this case, we're gonna show the first name, last name. We may back up and do an avatar as well. So just the the thumbnail, the avatar, I could move these around just by using edit raw value. You'll see these are just, the standard mustache syntax that you see throughout Directus as well.\u003C/p>\u003Cp>Excuse me. Let me get this a drink of water. I'm actually dying. That's a turn of fate. Okay.\u003C/p>\u003Cp>Alright. So we're gonna save this. Deal owner already exists in deals. Okay. Alright.\u003C/p>\u003Cp>Great. Let's move this around. Maybe we make deal stage half width. We slide deal owner up there. Let's actually take a look at this now.\u003C/p>\u003Cp>I should be able to assign folks. So let's create a new user. We'll just call it sales rep. Salesrep@example.com. And maybe we give them a nice avatar.\u003C/p>\u003Cp>Right? Sales rep, avatar. Let's just see what Google comes back with. John's inside sales rep. Yeah.\u003C/p>\u003Cp>This looks this is perfect. This is my guy right here. Alright. There's his avatar. We'll just save that.\u003C/p>\u003Cp>And now we can see who we've assigned this particular deal to. And now maybe within the deal card user here, I wanna show who that deal owner is. Great. Mister sales rep. Looking good.\u003C/p>\u003Cp>Alright. One of the other things that we need to do on our activities, we probably got a an owner of that activity or assigned to. It's been assigned to somebody on the team. Again, that's going to be assigned to a direct us user. We'll save that.\u003C/p>\u003Cp>And lots going on here. Just some type of weird glitch. I could see a couple of extra collections. I'll just remove these. Alright.\u003C/p>\u003Cp>Cool. So now weive got a deal. Weive got a table full of activities. I can go ahead and add these, like, follow-up on proposal. This is assigned to Mr.\u003C/p>\u003Cp>Sales Rep. We've got the activity type. We can see that's going to be, just a quick phone call. This is completed. We can see that conditional, conditional formatting for that.\u003C/p>\u003Cp>And we can add a due date of, let's say, the next Friday. Great. Save that. Keep editing. Cool.\u003C/p>\u003Cp>Alright. So, now, we've got the basic inner workings of a CRM. Right? We've got our deals, we've got contacts, we've got organizations, we've got our different pipeline stages. Right?\u003C/p>\u003Cp>If I wanted to organize these things a bit, we could go in and add different icons for each of these. So, you know, maybe we set some people icons for our contacts. We've got our organizations. Do we have an organization option? Let's look and see.\u003C/p>\u003Cp>Business. Is there a business? There you go. That looks somewhat like a business. We've got activities.\u003C/p>\u003Cp>Maybe this will be like a checklist. Cool. We've got our deals. Let's make those the money. Dollar signs, that's great.\u003C/p>\u003Cp>And then, deal stages, to me this is like a settings. Right? So I could create a new folder, let's just call it settings folder. You don't necessarily have to add this, but maybe we just do to keep it clean. And we look for, like, a settings icon just to use here.\u003C/p>\u003Cp>That's great. This one looks magical. Settings suggest. Right? And, again, I can change the name of this to just say settings.\u003C/p>\u003Cp>So, it still creates this collection, but, we can call it whatever we want. So we'll drag this up within settings folder. We'll drag deal stages and, this will be, what, like the Kanban view. There we go. Awesome.\u003C/p>\u003Cp>Okay. So now we get a little more organization to this. One of the other things that you might do and, you know, that you use all the time within ACRM are saved views. Right? So Directus gives you that ability with bookmarks.\u003C/p>\u003Cp>We'll just go in to the top here and maybe I want to sort by a specific sales rep. Right? Like, the deal owner is, a specific person and specific name is sales rep. That's the only one at this point. But I could go in and create a bookmark for this, and we could call it, deals sales rep Man.\u003C/p>\u003Cp>And we can change this up, give it a color. Now, within that collection, now we can see we've got our deals for Sales Rep Van, and I could save that bookmark. So even if I go into the main deals view, and maybe we change this back to a table view, which could be easier for, you know, maybe a sales manager or something who's controlling this. So I had deal owner back to this as well. Now if I go back, deal sales rep man view, boom, there it is.\u003C/p>\u003Cp>It's saved. I could go in and update this if I wanted to as well. So now we've got our pipeline. When we go into each one of the deal, we have our name, we've got our organization, we've got the notes, we've got our activities. You know, we can mark these activities off as completed.\u003C/p>\u003Cp>That seems like a great CRM baseline. Let's let's take a look at where we're at. Right? We got, like, 16 minutes on the clock. This feels like a win.\u003C/p>\u003Cp>I don't I don't know if we wanna run that one, let's discuss where we could go from here, right, maybe we want to automatically send some emails when it hits a certain stage in the pipeline. So let's just call these things done. Right? We can manage all of our contacts. We can manage all of our organizations.\u003C/p>\u003Cp>We can manage our sales pipelines. We can track our activities and follow-up. So let's say, you know, we get a new deal inbound. Maybe we want to automatically assign that to a a particular person, or we want to send a notification to our sales rep when that assignment happens. Let's figure out how to do that.\u003C/p>\u003Cp>Right? So I'm gonna go in. Let's just create a new deal. We'll say actually, let's wait a moment. Let's go into our flows.\u003C/p>\u003Cp>This is a good example. Right? Whenever a a new deal comes inbound, we want to send an email notification to our sales rep to to let them know. Alright. So we're gonna create a new flow.\u003C/p>\u003Cp>We'll just call it new inbound deal. Pretty straightforward. We could change this to the new symbol if we want to and just do a trigger setup. So what are we going to choose here? Directus gives you a ton of different options, as far as what to use when you're creating a flow.\u003C/p>\u003Cp>In this case, we're going to use the event hook. So when a certain event happens inside the platform, we wanna trigger an automation. The type that we're gonna choose here is action non blocking because we don't, the the filter allows you to basically either adjust the payload when a new deal gets created or a new event happens. Action non blocking, again, that runs after a create or update action. So in this case, let's do the items dot create.\u003C/p>\u003Cp>Whenever we're gonna trigger this based on the deals collection. And cool. Alright, so now I'm just going to save this, right? I'm going to go in and let's create a new deal inside the system. It's in the new stage.\u003C/p>\u003Cp>We're gonna assign this to mister sales rep. New deal automation. And we add, let's set this one to be for the little league. Again, we choose a specific contact, add some notes, and, maybe, the deal value, we'll just ballpark it at $5,000. Great.\u003C/p>\u003Cp>If I go back, now I can see that in my logs, I've got this flow. Here's the payload of this particular flow, and we could see who the deal owner is on this particular deal. How do we send an email notification to that specific owner? Right? I'm just gonna take this, copy it, and I open up just my Versus code editor where I've got my 100 apps, just, Docker Compose file here set up to run this locally.\u003C/p>\u003Cp>I'm just going to save that in case I need it. And, next, let's flesh this out a little bit. Right? So you can see the data we're getting back here. We're probably gonna need to look up that specific user.\u003C/p>\u003Cp>We can find them there. Those are the deal owner. We could send them a notification inside the app or we could send them a notification via email. Right? There's 2 options there.\u003C/p>\u003Cp>We've got notification. In this case, we've got the UUID of the user that we're going to send that to. So, you know, potentially, you want to send that in app. App. In this case, if there's a new deal, they're probably gonna be in their inbox.\u003C/p>\u003Cp>We're going to send that new deal to them. But let's actually find that email address first, though. Alright. So we're gonna read data from a specific collection. Let's call it Find User is the actual step we're gonna do here.\u003C/p>\u003Cp>For the permissions, let's just give full access. And, for the collection, you can see I don't have the Directus Users collection here, but I can go in and edit my raw value and just use Directus underscore users. And for our IDs, right, what are we gonna put here? So if I open this back up, we're gonna use the trigger. Payload.\u003C/p>\u003Cp>Dealowner. So we'll do this, we'll do trigger dot payload dot deal underscore owner. And I'm gonna wrap this in mustache syntax. And let's try this again. So we'll read the user.\u003C/p>\u003Cp>Let's go ahead and maybe just add this send email as well. So we'll send the email, and this is gonna be the read underscore user. We're using the key that we set of the previous operation within that flow. Readuser. Email should be.\u003C/p>\u003Cp>We'll input that. New deal assigned to you. And hey. Read_user.firstname. We've assigned a new deal to you.\u003C/p>\u003Cp>And then I can even go through and add those different variables if I wanted to for things like the deal name or the deal ID and and add a link back to that. So let's just try trigger. Payload.deal_name. Great. We'll save it.\u003C/p>\u003Cp>And now let's just test this out. I'm not actually sure if I've got emails set up here locally on this particular instance though, so that could be fun. But we should be able to actually see if this runs. In light of that bit of news, because it just looking at my configuration here, I do not have email configured here locally. So let's let's detach that one, and let's just test the notifications.\u003C/p>\u003Cp>Send notification. Cool. Send notification. The find_user.id. Cool.\u003C/p>\u003Cp>Full access. And it was a new deal assigned. We'll just add the key here. So that'll be trigger dot payload. Or no.\u003C/p>\u003Cp>Actually, it may be something like this where we have key. Alright. Let's just take a look just to make sure. Alright. Within our payload, we can see the key there.\u003C/p>\u003Cp>That's good. In that case, we probably didn't need to get the actual user there, but that's okay. I'm just gonna copy this message that we set here. Just paste that. And let's take a look at where this gets us.\u003C/p>\u003Cp>Alright. So we've got a new inbound deal. We're going to read the data of the user that we've assigned that to and we're going to send a notification to that specific person. Alright. Now we've got a new deal, deal stage.\u003C/p>\u003Cp>Let's just set it to new. Deal owner. I'm gonna choose myself here so we could test this notification. Say deal name. Test deal.\u003C/p>\u003Cp>It's worth $6,000,000. Very nice deal. We got a primary contact. Here's some notes. We'll just save that and let's see what happens.\u003C/p>\u003Cp>Right? We go to our Flow, we get a new inbound deal, we can see our logs, send notification, recipient is so and so. Hey, undefined. We've sent you a new test deal. Underscore first name.\u003C/p>\u003Cp>Why did that not come back? Read underscore oh, that's why I used the wrong key. Sometimes that happens. Find user dot first name. Great.\u003C/p>\u003Cp>So now, if I look and I check my activity log, I can see that I've got this new deal signed. Here's the notification for that. And if I click on it where it says view content, it should take me to that specific test deal. Great. Awesome.\u003C/p>\u003Cp>Let's call that a win. Right? We've got our custom CRM built out. We've set up some automation for this. We could go further and flesh this out a a ton of different ways.\u003C/p>\u003Cp>Right? If we talk about it, like, we could go through and send emails to our actual primary contact when it reaches a certain stage, that would be fairly easy to do using direct dis flows. You know, we could maybe even go as far as, like, a future future iteration of this could be setting up an inbox to parse incoming emails like a BCC functionality and add these to those specific deals as well. But this this feels really good. I'm I'm calling this a win.\u003C/p>\u003Cp>That's our custom CRM. Thanks for joining me on this episode of 100 apps, 100 hours. We'll catch you on the next one.\u003C/p>","Hi. Welcome back to another episode of 100 apps. 100 hours where we build some of your favorite apps or try to rebuild some of your favorite apps in 1 hour or less, or get publicly humiliated trying. Alright. Super excited to be back for another season. If you are new to 100 apps 100 hours, there are 2 basic rules. Number 1, you have 60 minutes to build and plan and build, no more, no less. So when that clock strikes 0, that's it. And then the second rule is there are no other rules. Use whatever you have at your disposal to complete the functionality. That's it. Let's dive into this episode. So today, I've got a custom CRM. Tools like Salesforce, Pipedrive, HubSpot, they really need no introduction. Everybody needs a CRM. A lot of them, depending on your purposes, may be way too much for what you need or they may not be specific enough for your industry. So we are going to build our own custom CRM in 1 hour or less. Let's do it baby. Alright. So, let's start the clock and away we go. So, when we plan our CRM, what do we need out of a CRM? Right? What kind of functionality do we want to see inside our CRM? So basically, we want to manage all of our contacts. We want to manage all the different organizations those contacts belong to. What else? We wanna manage our sales pipeline. Manage sales pipeline. That's gonna be deals or activities. We want to be able to track activities and follow-up. So this seems like a a pretty good set of functionality for a basic CRM. I'm sure we might embellish this a little bit depending on how far we get, but let's dive into actually fleshing out what the data model just might look like for something like this. So we'll drag a nice square up here. We're gonna have contacts. We could just call those people. I'm a big fan of that. We're gonna have organizations. There's definitely gonna be a relationship between those 2, but I I feel like this could be a many to many relationship because one contact could belong to multiple organizations, and and some CRMs make that a little more difficult to do than others. What else do we have? We're gonna have deals or opportunities. Deals is kind of a a standard nomenclature. We're gonna have activities that are attached to what, those are attached to organizations, they're attached to contacts, they're probably also gonna be attached to deals. What else are we going to have? We're probably going to have some sales reps, those are going to be our users inside our accounts. This looks pretty good for a a base set of functionality. Let's dive in and actually start building something. So, I'm just gonna pull up my Directus instance, that's what we're using on the back end. Totally blank instance, we'll zoom in on this, and let's start building. Right? Let's create our first collection. Let's start with contacts. So we're gonna give this a contacts as the name. For the primary key, let's use generated UUID. What kind of fields do we wanna add for our contacts? Right? When we think of contacts, we're gonna need name, email, but we may also wanna track when this was updated, when it was created. Directus makes that super easy. And one tip that you may not have realized if you've used Directus before, is I can go in and change these to be whatever I want. So if I wanted this to be created at and created by, I could go in and update those, change them to be whatever I want. Updated at, updated by. That's the the nomenclature that you're used to. Do we actually need a sort for our contacts? I don't think. Let's just go ahead and save this. Alright. So for our contacts, we're gonna add a first name. Great. We'll make that a string. Let's do half width for that. I can go in and click the three little dots here and just quickly duplicate this field to save myself some time. And, of course, as I am plotting away on this, Directus underneath is mirroring all these changes to my database schema. So we've got our first name, we've got our last name, we're gonna need a email or an email. Great. Let's require this value. And, you know, I could even go as far as, like, making this unique if I I wanted to have some type of unique identifier to match these up with. Alright. As far as an interface, you know, I could get fancy with this and we could add a little email icon like an at symbol. That's great. I think everything else is good. You know, one of the other nice things that's built into Directus is I can go in and add my own custom reg x validation. So, you know, I can put in some type of reg x that matches email addresses. I don't have one of those handy here, but we'll go ahead and keep marching along here. So we got first name, we got last name, we got email. We'll probably have a phone number at some point, but let's let's go for, like, a job title, probably. I'm trying to think of all the standard fields. Maybe we have some notes. We could set that to be a text area field. I don't really need formatting for those. Great. Job title. And maybe honestly, let let's strip this out because I'm I'm thinking about that many to many relationship with organizations, and we may have, different titles at different organizations. So I'll show you how we can handle that coming up. Alright. So we got first name, we got email. We could go ahead and add phone number as a string. Phone or phone number. Let's just keep it short. We'll say phone. Let's give it a phone icon. Great. K. Phone, email. We'll put email above phone. Cool. Looks nice. Alright. Let's just take a look. Right? We've got first name. Go ahead. Oh, I was gonna add my wife here, but she's gonna get mad at me if I misspell it. Right? Ashley atexample.com. I had a phone number, 555-5555. Here's some nice notes for our contact. These are looking great. Right? We've got our contact in here. I could potentially sort and filter this a a hundred different ways, maybe change the layout. But let's dive into the next collection that we wanna set up, our organizations. So we'll have organizations, again, for the primary key, I'm gonna use ID and just use generated UUID as the type. And, again, I could change created at I could change this structure for these system fields to be whatever I want. Created by, date updated, that's gonna be updated at, if I can actually spell. And we use this one as well and call it updated by. Great. Okay. So now we've got an organization. For our organization, what are we gonna have? We're gonna have a name of the organization. Right? Probably, some addresses for that organization. Right? We could have multiple. So that's where we might reach into an another bag of just a different table. What else are we gonna have for an organization? You know, let's just do something like country in case we want to, even, like, automatically route new organizations to a specific sales rep. Potentially, we've got a name. What are we gonna have? We're gonna have a website. You know, we may have a a logo if we wanna track that for a company. So let's do an image file. Let's call it logo. You know, you might even add things like brand color, things like that if if you're really in tracking that. And then we'll just add another section for notes. This is gonna be a text area field with the type text, and we'll hit save. So we've got our organizations. We've got our contacts. Let's make a link between the 2. Right? So how do we go about that? Depends on how you want the data to actually be structured. Right? In an application, depending on the setup, maybe a user can only belong to one specific organization. But often inside of CRM and maybe I am working with the local little league. I am a member of the board there, and then I'm also a developer advocate here at Directus or, you know, a a founder at Better Side Shop. So a lot of different relationships that is gonna be modeled with the mini to mini relationship inside Directus. Here's how we set that up. It's gonna be pretty easy. We'll just go to create field. We'll look for a many to many relationship. And because we're on organizations, our key here is gonna be contact for the or contacts, I should say. Our related collection is gonna be contacts and then we'll just go through and paint by numbers here. Do we want to show these in a list or a table? I'm good either way. We definitely want to show a link to that item so we can get to that specific contact, but I'm gonna pop open this advanced field creation mode just to show you that you do have control over the naming of the junction collection and the individual fields within that. So, the default setup here is just going to take this table name and this table name or this collection name and this collection name and marry the 2 together and we end up with organizations underscore contacts. Works for me. We'll leave that autofill set up. And then on the reverse, I'm gonna add that many to many field to the contacts as well. We're gonna keep that as the organizations. Alright? In this case, we may have a sort field. So we wanna control the the sorting for contacts, like who's 1st, 2nd, 3rd, that could be helpful in, like, a primary contact situation. And then we have some of our relational triggers. Right? On deselect of organizations' contacts, what do we want to do here? Yeah. Maybe I want to delete that association. So I'll set these to cascade. Great. So now that back out, we can see we've got our contacts field here. If we go into, yep, looks like we've got, an extra contact table created. I might have typed something wrong. But, so we can see contacts there in the organizations. And then on our contact, I should see organizations here. Let's just add an organization. Right? So let's say my wife worked at Tesla. That's in the United States. And, you know, we could add the website, logo, notes. I could choose existing contacts. But I'm just going to go ahead and say 1. Right? So now, I could see I've got that organization here, but it's showing an ID, which is not super helpful. Right? So I can go into the organization itself and control the display template where I have a name. I could even potentially add the logo to that if I wanted. So now if we check it again, we can see, okay, here's the organizations that they're a part of. Right? Now, you also have the ability to manage the data inside that junction collection. Right? So if I go in and and like I said, I may have a different job title at all these different organizations. So I could go in and add a new field here inside this junction collection for job title. Great. Alright. So, now if we go into Tesla, we can see the job title is down here at the bottom, but Directus also gives me an easy way to control where that displays. So if I go to our many to many relationship inside our contacts, I can go to maybe we wanna add a sort field for this just to make sure. And then if I go to the interface, I can control where my junction fields are located. So I can put this at the top, which should be great. Okay. So now if we just check that out one more time. Right. Now I could see director of DirectUs. So now I could give a specific job title to this specific person within that organization. I could go in and add a new organization as well, like, hey, the little league. We can say a board member. Right? Little League Baseball is the name of the organization. Great. Okay. So now there my wife is a part of 2 organizations, different job titles for each. Right? Alright. How are we looking on time? We're looking great. Let's move on to our next collection that we want to set up. That's gonna be what? Our deals, our activities, what do we want to set up next? Let's go for deals or opportunities. Deals is probably the standard naming for this. So that's what we'll stick with. Could be opportunities. Could be something else. That's fine. We'll do created at. Just to keep the same structure, we'll do created by. And by adding these, whenever this record is created, it's going to populate a timestamp and a user. The same thing for Updated, whenever it gets updated or who updates it, it's gonna populate that info for us. Updated by. Great. Okay. So our deals what we're gonna have for our deal? We'll probably have a name or a title of the deal. Deal name sounds great. It could be good to prefix some of these sometimes if you're dealing with a lot of nested relational data. So you get used to seeing name in there a 100 times. Could be confusing whether that's the actual deal name or the organization name or the contact name. So we'll just save the deal name. What else are we gonna have on this deal? Right? We'll probably have a average dollar amount or a potential dollar amount. Let's set that to be, decimal. Deal value. We can add a nice little dollar sign to the icon to make this look nice and pretty. And then we are going to have some related fields. But before we do that, let's just add, like, some deal notes or something like that. Sounds great. Okay. So we got a deal name, we got a deal value, you know, maybe these are side by side, not a big deal. Let's go in, and now we want to add a relationship to the organization and probably to our primary contact for this deal. So those are both gonna be many to one relationships. And we'll set a key of organization here. So the related collection, we'll set that to be organizations. Great. And I could control the display template here. We'll just use the name and we hit save. So now we've got an organization that we're gonna tie to the deal. I'll make that half width. And then we also want to add a primary contact. So we'll just say primary contact. The related collection here is going to be the Contacts collection. And for the Display template, let's use first name, last name, and let's use this format, which I think is typically how it displays inside an email client where you have the email address inside these less than or greater than SQL or symbols. So let's save that. We've got our primary contact. We've got our notes. We've got our deal. What do we need next? Right? We need to track where that deal is at. So we could use, like, a a status field as, like, a drop down for that potentially. Right? What's the status of this particular deal? Blah blah blah. Or we could do something else where we have a relationship to a deal stage or our individual pipeline. Right? Because then we may want to, potentially have separate pipelines or we we wanna give more control back to our users, so the the sales reps or the sales manager to control that actual pipeline without getting into the admin section. So how can we do that? Right? Looks like I've got a little extra couple of fields that I've been creating here inadvertently. Alright, so now let's add a new collection. Let's just call it Deal Stages. We want to manage what are the stages of a particular deal. As far as the optional fields, maybe I don't really not super concerned with these, deal stages. We're gonna call this the name of this particular stage. Maybe we wanna give it a color so we can add some color to it. And, you know, I guess we could even give it an icon if we wanted to. We could play around with that and see what that looks like. Deal stages. Let's make both of those half width. And, now, let's just go through and map some of these particular stages. Right? And I and I, you know, I've got this s on here. One of the other things that you could do inside Directus, you can't control the translations for all of these. So even if you're working in English, on the the back end, you may want to use prefix tables to keep things nice and organized as a developer. This is a great way to control what displays via the interface to your actual users within the application. So I could just call this deal stages or pipeline stages, whatever makes a lot of sense here, and hit save. Alright. So it still shows me deals_stages here, but when I start looking inside the actual app, there we go, we can see our pipeline stages. So, let's create our first one. Right? Let's just say, New, this is inbound. Do we have a symbol? We do have a symbol for this. Right? So let's say red. This is we need to take action on this. What's next? Right? Qualified, maybe? Contacted? Or, let's say assigned. Right? We forgot to assign those to a sales rep. Assign assignment. That's a nice looking icon for that. Then we'll say qualified. You know, if we're doing software sales, like, a check mark. Okay. What else? Demo. Let's save green. Oh, let's make this orange. Do we have like a demo? Maybe a TV? What have we got? Yeah, there we go. That works for a demo as far as the icons. And then we are going to set, like, a last stage, like, proposal. Great. Let's just go gray. Cool. Document. Awesome. Alright. So I can control the way that these are displayed through my actual settings, here. So I can go into each specific one. And on the display tab, let's display a colored dot for the color. And then for the icon, we're just gonna display the actual icon. Alright. Great. If we take a look now, I can see what these actually look like. Cool. And then for my display template for the deal stages, I may even go in and just mirror that same structure where I have a color, then I have an icon with a name. Okay. So, now, what we've done, we've effectively given control over these, and we probably actually need a sort on these as well, so so we can control that value. So, let's go in and quickly add a sort. That's going to be an integer value. And because this is coming after the fact, after we've created this actual table, I'm just gonna scroll down a little bit, and I will have a sort field here. So we'll choose sort just to make sure we manage that. And now I should be able to drag and drop these, in whatever order that I like. Again, we're giving control back to the users of our custom CRM. We're definitely gonna prevent them from getting inside the admin so they don't mess with the data model. But here, we give them the ability to control what those pipeline stages are gonna be. So now we need to add those to our actual deals. Right? So we're gonna go into our deals. We're going to create a many to one relationship. And, you know, this is going to be the deal stage or just stage. And we're gonna use deals underscore stages as the related collection. We can open this up and and just see what's going on. I I don't really need to create that inverse relationship. I could. Doesn't make a ton of sense, though. I'm not sure. Alright. Display related values, validation. Let's keep it very simple, and then we're gonna use our deal stage. Alright. So now if I go into our deals Why is this oh, it goes in the machine. I don't know what's going on on this particular example, but it keeps creating additional fields that we don't need. Alright. So let's create a new deal. Alright. This is gonna be a new deal. We can see, hey, there's our stage. That looks great. The deal name is 100 apps, 100 hours contract. It's worth $1,000,000. So we'll add that. How many zeros do we have there? 1, 2, 3, 4, 5, 6. Alright. Let's make this for the Little League team. Great. My wife is the primary contact. Here's some notes on this deal. Great. Did we save it? Numeric value and deals is out of range. Uh-oh. Wouldn't be a 100 apps, a 100 hours without, some type of issue. Right? So let's take a look at this. The deal value, what did I set? Precision and scale. Maybe we bumped this up way up. Probably could have gone with integers for this as well. Do I have a minimum and a maximum? I I don't have that set. So not entirely sure what's going on. Let's just try this again. 100 apps, 100 hours contract, 1 100,000, 1,000,000. Try it again. Save. Okay. Alright. So now, we've got this particular deal set up. This is kind of an underwhelming view. Right? And, again, I might control how these things display. So if I just go into our deals, we go to the deal stage. If I wanna control how it displays, I can go in and set this. So let's do the color, do the icon, do the name, just because, I'm a I'm a very visual person, and I suspect, a lot of your end users may be as well. We still don't see the organization there. So, again, we'll go back. We'll adjust our organization. We wanna display related values. We want to show the name of that specific organization. And let's get even fancier and maybe we wanna show that logo. I'm gonna click on this and then you'll see this one that has a little magic beside it that's a thumbnail. That's just a shortcut for, like, a nice thumbnail sized image instead of loading the actual image size. So I donit have a logo for this company, but I could quickly find 1. Alright. So, we'll just copy this image address, go back to our instance, let's load up that specific company, they who shall not be named, Hit import. Oh, that's a data URL. Let's find an actual URL that we can just copy. Great. Let's try this again. We can import via URL. Cool. And now when I'm looking at my deals, I can see the logo of that organization. That makes it really handy, when I'm working on a deal just to have that extra extra visual reinforcement. Now, when I'm looking at deals, I may want to set up like a traditional pipeline type of view. So I could do that really easily just by switching the layout here inside Directus. So we'll change this to a Kanban layout. Let's group by the deal stage and the group title is gonna be the name. So I can see new assigned qualified demo. We have that Kanban view that we're used to. As far as the text, what do we wanna look at? We want to control, let's say, when this was created. I'm not really concerned about that. The tags, what do we want to set that to be? Do we want a card image? Honestly, this looks okay. Do we need an actual user on here? Probably not. Cool. Alright. We'll just keep it as is and let's let's start fleshing this out a little more as well. Right? So within a deal, when I'm working this deal, we're going to have activities assigned to this as it moves across the pipeline. And, for that, let's just go in and add a new table, a new collection, we're going to call it activities. Activities, I think that's the correct spelling. We use generated UID, we'll do created at, createdby, create up, updatedat, updated by. Great. And, in this case, maybe we do give a status for this particular activity. Right. Status, great. Let's give a name of the activity, probably a type of activity. Makes sense. We use a drop down for that. It seems pretty straightforward or, you know, maybe they're not gonna need to change the activity types on a a very frequent basis. So we'll call it the activity type, and let's add a couple choices to this. So this is a relatively new addition to Directus within the drop down, the ability to have an icon and a color. So let's say text is a phone call, value is a phone. One of the other cool things if you want to translate this value, you could use this phone t, or this dollar sign t for a translatable string. And then anytime you have users who are using the app within a different language, inside the settings, you can control all those custom translations. We'll take a look at that in a moment. Let's call this what? Phone call. The value could just be phone call, depending on how I wanna store this. Right? It may have an underscore within it as well. It could just be the same thing. Either way. Look for that phone. Great. And I could even add a color for that if needed. Maybe I just wanna keep these all the same. Phone call, we'll call it a meeting. And for this, do we have a meeting icon? Right? We calendar looks nice. Maybe we want to track demos separate from meetings. Alright. So we drag a demo. That was the TV icon that we had. Great. What else are we gonna need? Like, an email? Not sure I would Yeah. Maybe, like, a follow-up email. We wanna reschedule that. And then we'll do an email. Great. Demo, phone call. I think I'm gonna set this to be underscore value. Cool. Alright. Looking good. Activity type. Okay. What else are we going to need for this? We need a due date for this activity and the status, we can use as as is. It's published draft, archived. What do I really care about this activity? It probably isn't completed or not. Right? So maybe we scrap status, and maybe we just go for a toggle instead. Right? Hey. Is this completed? The default value, great. For our label, maybe we change it to completed and give it a color of color on. Color off is red, just to show that it has not been completed. Great. And let's just clean up this form a little bit, making sure everything looks nice. Okay. We are going to add a date for this, and let's set a specific date and time that this thing occurred. Call it due date, end date. Due date seems reasonable, but when do we actually need to complete this specific task? Okay. So now we've got an activity. We want to, link this to our actual deal so we can track those. Right? So, what I'm gonna do in this case is create a mini to one relationship because this activity could only belong to a single deal. Right? We're gonna use the key of deal. The related collection, we'll set that to deals. We'll show the name of that deal. Maybe we show the actual organization as well so I can actually dig into the related collections and and show values from that? Excuse me one second. Sorry about that. Cool. Alright. So now, what I forgot to do is create that inverse relationship. You can actually set that up via direct us when you're creating that relationship. But now I can also just go into our deals, not deal. We'll go into our deals, and now we're gonna create a one to many relationship back to those activities. So we'll call this key of activities. We've got activities. The foreign key will be deal. That already exists. Maybe we wanna show these in a table. We'll choose the columns, due date, Name, Due Date, Activity Type. Seems pretty savvy. And I can even filter these, right? So if I wanted to see just activities where they were not completed. We can enable search and filtering and show a link to these. We'll take a look at what all these look like in just a moment. But we've forgotten one important step through this whole process is, hey. We need somebody to assign these deals to. Right? So, let's add a many to one relationship. We'll call that the deal owner or, you know, you potentially say who this is assigned to. Like, the deal owners, again, kind of standard naming in these scenarios. And for the related collection here, we're gonna use directus underscore users. So these are gonna be actual users of the application that we're assigning this to. Invalid payload collections can't start with direct us users. Oh, deal owner. Let's go to our related collection, and let's get direct us underscore users. And, in this case, we're gonna show the first name, last name. We may back up and do an avatar as well. So just the the thumbnail, the avatar, I could move these around just by using edit raw value. You'll see these are just, the standard mustache syntax that you see throughout Directus as well. Excuse me. Let me get this a drink of water. I'm actually dying. That's a turn of fate. Okay. Alright. So we're gonna save this. Deal owner already exists in deals. Okay. Alright. Great. Let's move this around. Maybe we make deal stage half width. We slide deal owner up there. Let's actually take a look at this now. I should be able to assign folks. So let's create a new user. We'll just call it sales rep. Salesrep@example.com. And maybe we give them a nice avatar. Right? Sales rep, avatar. Let's just see what Google comes back with. John's inside sales rep. Yeah. This looks this is perfect. This is my guy right here. Alright. There's his avatar. We'll just save that. And now we can see who we've assigned this particular deal to. And now maybe within the deal card user here, I wanna show who that deal owner is. Great. Mister sales rep. Looking good. Alright. One of the other things that we need to do on our activities, we probably got a an owner of that activity or assigned to. It's been assigned to somebody on the team. Again, that's going to be assigned to a direct us user. We'll save that. And lots going on here. Just some type of weird glitch. I could see a couple of extra collections. I'll just remove these. Alright. Cool. So now weive got a deal. Weive got a table full of activities. I can go ahead and add these, like, follow-up on proposal. This is assigned to Mr. Sales Rep. We've got the activity type. We can see that's going to be, just a quick phone call. This is completed. We can see that conditional, conditional formatting for that. And we can add a due date of, let's say, the next Friday. Great. Save that. Keep editing. Cool. Alright. So, now, we've got the basic inner workings of a CRM. Right? We've got our deals, we've got contacts, we've got organizations, we've got our different pipeline stages. Right? If I wanted to organize these things a bit, we could go in and add different icons for each of these. So, you know, maybe we set some people icons for our contacts. We've got our organizations. Do we have an organization option? Let's look and see. Business. Is there a business? There you go. That looks somewhat like a business. We've got activities. Maybe this will be like a checklist. Cool. We've got our deals. Let's make those the money. Dollar signs, that's great. And then, deal stages, to me this is like a settings. Right? So I could create a new folder, let's just call it settings folder. You don't necessarily have to add this, but maybe we just do to keep it clean. And we look for, like, a settings icon just to use here. That's great. This one looks magical. Settings suggest. Right? And, again, I can change the name of this to just say settings. So, it still creates this collection, but, we can call it whatever we want. So we'll drag this up within settings folder. We'll drag deal stages and, this will be, what, like the Kanban view. There we go. Awesome. Okay. So now we get a little more organization to this. One of the other things that you might do and, you know, that you use all the time within ACRM are saved views. Right? So Directus gives you that ability with bookmarks. We'll just go in to the top here and maybe I want to sort by a specific sales rep. Right? Like, the deal owner is, a specific person and specific name is sales rep. That's the only one at this point. But I could go in and create a bookmark for this, and we could call it, deals sales rep Man. And we can change this up, give it a color. Now, within that collection, now we can see we've got our deals for Sales Rep Van, and I could save that bookmark. So even if I go into the main deals view, and maybe we change this back to a table view, which could be easier for, you know, maybe a sales manager or something who's controlling this. So I had deal owner back to this as well. Now if I go back, deal sales rep man view, boom, there it is. It's saved. I could go in and update this if I wanted to as well. So now we've got our pipeline. When we go into each one of the deal, we have our name, we've got our organization, we've got the notes, we've got our activities. You know, we can mark these activities off as completed. That seems like a great CRM baseline. Let's let's take a look at where we're at. Right? We got, like, 16 minutes on the clock. This feels like a win. I don't I don't know if we wanna run that one, let's discuss where we could go from here, right, maybe we want to automatically send some emails when it hits a certain stage in the pipeline. So let's just call these things done. Right? We can manage all of our contacts. We can manage all of our organizations. We can manage our sales pipelines. We can track our activities and follow-up. So let's say, you know, we get a new deal inbound. Maybe we want to automatically assign that to a a particular person, or we want to send a notification to our sales rep when that assignment happens. Let's figure out how to do that. Right? So I'm gonna go in. Let's just create a new deal. We'll say actually, let's wait a moment. Let's go into our flows. This is a good example. Right? Whenever a a new deal comes inbound, we want to send an email notification to our sales rep to to let them know. Alright. So we're gonna create a new flow. We'll just call it new inbound deal. Pretty straightforward. We could change this to the new symbol if we want to and just do a trigger setup. So what are we going to choose here? Directus gives you a ton of different options, as far as what to use when you're creating a flow. In this case, we're going to use the event hook. So when a certain event happens inside the platform, we wanna trigger an automation. The type that we're gonna choose here is action non blocking because we don't, the the filter allows you to basically either adjust the payload when a new deal gets created or a new event happens. Action non blocking, again, that runs after a create or update action. So in this case, let's do the items dot create. Whenever we're gonna trigger this based on the deals collection. And cool. Alright, so now I'm just going to save this, right? I'm going to go in and let's create a new deal inside the system. It's in the new stage. We're gonna assign this to mister sales rep. New deal automation. And we add, let's set this one to be for the little league. Again, we choose a specific contact, add some notes, and, maybe, the deal value, we'll just ballpark it at $5,000. Great. If I go back, now I can see that in my logs, I've got this flow. Here's the payload of this particular flow, and we could see who the deal owner is on this particular deal. How do we send an email notification to that specific owner? Right? I'm just gonna take this, copy it, and I open up just my Versus code editor where I've got my 100 apps, just, Docker Compose file here set up to run this locally. I'm just going to save that in case I need it. And, next, let's flesh this out a little bit. Right? So you can see the data we're getting back here. We're probably gonna need to look up that specific user. We can find them there. Those are the deal owner. We could send them a notification inside the app or we could send them a notification via email. Right? There's 2 options there. We've got notification. In this case, we've got the UUID of the user that we're going to send that to. So, you know, potentially, you want to send that in app. App. In this case, if there's a new deal, they're probably gonna be in their inbox. We're going to send that new deal to them. But let's actually find that email address first, though. Alright. So we're gonna read data from a specific collection. Let's call it Find User is the actual step we're gonna do here. For the permissions, let's just give full access. And, for the collection, you can see I don't have the Directus Users collection here, but I can go in and edit my raw value and just use Directus underscore users. And for our IDs, right, what are we gonna put here? So if I open this back up, we're gonna use the trigger. Payload. Dealowner. So we'll do this, we'll do trigger dot payload dot deal underscore owner. And I'm gonna wrap this in mustache syntax. And let's try this again. So we'll read the user. Let's go ahead and maybe just add this send email as well. So we'll send the email, and this is gonna be the read underscore user. We're using the key that we set of the previous operation within that flow. Readuser. Email should be. We'll input that. New deal assigned to you. And hey. Read_user.firstname. We've assigned a new deal to you. And then I can even go through and add those different variables if I wanted to for things like the deal name or the deal ID and and add a link back to that. So let's just try trigger. Payload.deal_name. Great. We'll save it. And now let's just test this out. I'm not actually sure if I've got emails set up here locally on this particular instance though, so that could be fun. But we should be able to actually see if this runs. In light of that bit of news, because it just looking at my configuration here, I do not have email configured here locally. So let's let's detach that one, and let's just test the notifications. Send notification. Cool. Send notification. The find_user.id. Cool. Full access. And it was a new deal assigned. We'll just add the key here. So that'll be trigger dot payload. Or no. Actually, it may be something like this where we have key. Alright. Let's just take a look just to make sure. Alright. Within our payload, we can see the key there. That's good. In that case, we probably didn't need to get the actual user there, but that's okay. I'm just gonna copy this message that we set here. Just paste that. And let's take a look at where this gets us. Alright. So we've got a new inbound deal. We're going to read the data of the user that we've assigned that to and we're going to send a notification to that specific person. Alright. Now we've got a new deal, deal stage. Let's just set it to new. Deal owner. I'm gonna choose myself here so we could test this notification. Say deal name. Test deal. It's worth $6,000,000. Very nice deal. We got a primary contact. Here's some notes. We'll just save that and let's see what happens. Right? We go to our Flow, we get a new inbound deal, we can see our logs, send notification, recipient is so and so. Hey, undefined. We've sent you a new test deal. Underscore first name. Why did that not come back? Read underscore oh, that's why I used the wrong key. Sometimes that happens. Find user dot first name. Great. So now, if I look and I check my activity log, I can see that I've got this new deal signed. Here's the notification for that. And if I click on it where it says view content, it should take me to that specific test deal. Great. Awesome. Let's call that a win. Right? We've got our custom CRM built out. We've set up some automation for this. We could go further and flesh this out a a ton of different ways. Right? If we talk about it, like, we could go through and send emails to our actual primary contact when it reaches a certain stage, that would be fairly easy to do using direct dis flows. You know, we could maybe even go as far as, like, a future future iteration of this could be setting up an inbox to parse incoming emails like a BCC functionality and add these to those specific deals as well. But this this feels really good. I'm I'm calling this a win. That's our custom CRM. Thanks for joining me on this episode of 100 apps, 100 hours. We'll catch you on the next one.","592c22ad-1cb2-4d65-9bd7-e0d7c98fe4f2",[185],"e9e66fa8-0650-4e37-ae8b-74755fdd5dca",[],{"reps":188},[189,245],{"name":190,"sdr":8,"link":191,"countries":192,"states":194},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[193],"United States",[195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244],"Michigan","Indiana","Ohio","West Virginia","Kentucky","Virginia","Tennessee","North Carolina","South Carolina","Georgia","Florida","Alabama","Mississippi","New York","MI","IN","OH","WV","KY","VA","TN","NC","SC","GA","FL","AL","MS","NY","Connecticut","CT","Delaware","DE","Maine","ME","Maryland","MD","Massachusetts","MA","New Hampshire","NH","New Jersey","NJ","Pennsylvania","PA","Rhode Island","RI","Vermont","VT","Washington DC","DC",{"name":246,"link":247,"countries":248},"Michelle Riber","https://meetings.hubspot.com/mriber",[249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,226,437,438],"Albania","ALB","Algeria","DZA","Andorra","AND","Angola","AGO","Austria","AUT","Belgium","BEL","Benin","BEN","Bosnia and Herzegovina","BIH","Botswana","BWA","Bulgaria","BGR","Burkina Faso","BFA","Burundi","BDI","Cameroon","CMR","Cape Verde","CPV","Central African Republic","CAF","Chad","TCD","Comoros","COM","Côte d'Ivoire","CIV","Croatia","HRV","Czech Republic","CZE","Democratic Republic of Congo","COD","Denmark","DNK","Djibouti","DJI","Egypt","EGY","Equatorial Guinea","GNQ","Eritrea","ERI","Estonia","EST","Eswatini","SWZ","Ethiopia","ETH","Finland","FIN","France","FRA","Gabon","GAB","Gambia","GMB","Ghana","GHA","Greece","GRC","Guinea","GIN","Guinea-Bissau","GNB","Hungary","HUN","Iceland","ISL","Ireland","IRL","Italy","ITA","Kenya","KEN","Latvia","LVA","Lesotho","LSO","Liberia","LBR","Libya","LBY","Liechtenstein","LIE","Lithuania","LTU","Luxembourg","LUX","Madagascar","MDG","Malawi","MWI","Mali","MLI","Malta","MLT","Mauritania","MRT","Mauritius","MUS","Moldova","MDA","Monaco","MCO","Montenegro","MNE","Morocco","MAR","Mozambique","MOZ","Namibia","NAM","Niger","NER","Nigeria","NGA","North Macedonia","MKD","Norway","NOR","Poland","POL","Portugal","PRT","Republic of Congo","COG","Romania","ROU","Rwanda","RWA","San Marino","SMR","São Tomé and Príncipe","STP","Senegal","SEN","Serbia","SRB","Seychelles","SYC","Sierra Leone","SLE","Slovakia","SVK","Slovenia","SVN","Somalia","SOM","South Africa","ZAF","South Sudan","SSD","Spain","ESP","Sudan","SDN","Sweden","SWE","Tanzania","TZA","Togo","TGO","Tunisia","TUN","Uganda","UGA","United Kingdom","GBR","Vatican City","VAT","Zambia","ZMB","Zimbabwe","ZWE","UK","Germany","Netherlands","Switzerland","CH","NL",1773850451910]