[{"data":1,"prerenderedAt":439},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-airbnb":121,"100-apps-100-hours-airbnb-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},"30c48566-52cd-4728-a633-ca9675acc959","airbnb","908297167","Can Bryant follow AirBnb's trajectory from scrappy startup to short term rental royalty – and build an AirBnb clone in one hour or less? Follow along as he attempts to builds a complete backend and frontend to manage rental listings, bookings, hosts, and more.","58537616-bf5c-4251-ae41-115fae3d13df",63,[129],{"name":130,"url":131},"Bryant Gillespie","https://directus.io/team/bryant-gillespie",10,"2024-02-12","Mission: Airbnb","\u003Cp>Speaker 0: Alright. Alright. Alright. Welcome back to the next episode of 100 Apps, 100 Hours, where we build or rebuild some of your favorite apps, or publicly fail trying. I'm your host Brian Gillespie, developer advocate at Directus.\u003C/p>\u003Cp>And today we have the mother of all clones, Airbnb. This was actually going to be one of the first episodes that I was going to record for this series. To be honest, I got a little scared. Airbnb has a ton of functionality on the front end. We have authentication, we have listings, you have 2 different types of users, and that you have folks who are listing their properties, folks who are booking those properties, you have messages, you have reviews.\u003C/p>\u003Cp>There's a ton of things just underneath the surface. So I'm curious to see how far we can get in an hour. I don't want to bite off more than I could chew, but, should be entertaining nonetheless. Alright. Alright.\u003C/p>\u003Cp>So if you've tuned into the series before, you'll know we have two rules. The first is you have 60 minutes to plan and build, or I have 60 minutes to plan and build, if you've got 60 minutes, if you're watching along, maybe you want to build with me. It's great. No more, no less, that's all we get. And then the second rule is kind of the anti rule.\u003C/p>\u003Cp>Use whatever you have at your disposal. AI, chat GPT, I've got a Nuxt front end application starter set up that already has some plumbing to connect to my Directus Instance, and I have a blank Directus project. That's what I've got. We're gonna try to build this Airbnb clone. Let's get started.\u003C/p>\u003Cp>Alright, so we'll start the clock here and away we go, right? So I'm going to pull up the Airbnb website, if I can actually figure out how to work my computer, and let's just poke around and see what we can find, right? One of the first things that I usually like to do when I'm trying to break down functionality for an app is take a look at the navigation, right? Airbnb has this concept of stays versus experiences now, so we have properties or listings or something like that. We have experiences which are, tours and tastings and and actual things that you can do.\u003C/p>\u003Cp>So that should be an interesting dynamic. I I think we'll mostly probably just stick to the properties part of it, like the listings. You know, one of the other things that I always like to take a look at, you know, I can go in and check the network requests, you know, and and try to suss out what some of the the data model looks like for all these different things that we're querying on the front end. So it looks like there is a very, very interesting, set of queries they're using. If I look into, like, the actual property values, we've got this very detailed data that we're returning and using potentially for our layout.\u003C/p>\u003Cp>I don't really know how helpful this is going to be, right? And to my knowledge, I don't know that Airtable, or I'm sorry, Airbnb has a public API that I can use to kind of introspect the schema, so basically I'm just playing it by ear here, right? So on every episode, before I actually build, I like to do a bit of planning. And first, let's start with like what type of functionality we're going to try to achieve out of this. I certainly want to get the back end data model built for this, we'll map that out in just a moment.\u003C/p>\u003Cp>On the front end maybe we want to display a listing detail page and maybe a listing index page. Let's call that our original goal. If we got like a stretch goal, maybe we want to let folks book booking pages, booking properties, sending messages, something like that. So I think these three pieces here will be plenty to chew on for an hour. Let's, let's try to map this application out.\u003C/p>\u003Cp>And I really like Figma, FigJam, the combo is great for mapping out things like this. We'll just do no fill. Alright, so as far as our data model, naming things is always tricky. So are we gonna call this Listings, is it Properties? That's gonna be our main collection, right, this is our main table.\u003C/p>\u003Cp>Not a 100% sold on what it's gonna be yet. We've probably got users in the system, so we'll have our users and Directus is going to actually take care of that for us with the Directus users collection that that it bootstraps whenever we actually create our instance. What else do we have? We've got, reviews that are going to be included and it can be attached to the individual listings. Looks like we have categories as well.\u003C/p>\u003Cp>Right? Tiny Homes, Countryside, I don't know if those are gonna be tags. I guess you could be a Countryside and a Tiny Home. So that could be like a mini to mini relationship. So let's just call those tags.\u003C/p>\u003Cp>Great. We could tag those onto a property. We have bookings. Alright. So you've got a calendar of bookings for a property or listing.\u003C/p>\u003Cp>We can make those little relationships. Just draw some nice looking arrows, basically. That's all we're doing here. Then we've probably got a junction table for that mini to mini relationship. Cool.\u003C/p>\u003Cp>Just drag that out of the way. We've got our reviews. Tags. Okay. Listing tags.\u003C/p>\u003Cp>Alright. What else? If we open up filters, we could see things like, the type of place, the price range, bedrooms, number of bedrooms, property type, the amenities. That's kind of a nice bit of functionality. How are we going to deal with that?\u003C/p>\u003Cp>What are the booking options? Host language. If we go to the actual detail page for a property, right, we've got a gallery of multiple images, we've got a host that could be our user, I guess. You know, we might have a like a host profile or something like that, that we attach so that we don't pollute our users collection. We've got a schedule, we've got the reviews, we've got a map, yeah.\u003C/p>\u003Cp>Okay. I feel pretty good about this. Let's just draw a couple more arrows, just for the sake of arrows. Let's actually start building this out. Cool.\u003C/p>\u003Cp>Alright, so I'm going to drag this over and as you can see over here on the left I've got my blank instance of Directus. What are we gonna call this main one? Properties to me as a developer, I'm thinking like properties of an object or something like that, so I'm I'm gonna call this listings. If anybody from Airbnb is watching this, hey, maybe tell me what you're what kind of schema you're using on the back end. All right, so I'm going to create a new collection called Listings.\u003C/p>\u003Cp>We're going to use the generated UUID. Is this a published listing? Yes, probably. So we'll keep status, we'll have the created on, updated on, user created, etcetera. And let's go through and start modeling this out.\u003C/p>\u003Cp>Alright. So if I look, we've got a h one, this is probably just the title of the listing. Great. What happens to our URL? It looks like they don't use fancy URLs or like a slug for this listing.\u003C/p>\u003Cp>It's just actually by the ID. And it looks like it's rooms. I could have just looked at the URL. I kinda like listings better just because they are using, they're listing whole houses on here as well, right, not just a room. So, cool.\u003C/p>\u003Cp>We will have a, this looks like it's calculated. What are the other things that we need? The number of guests, the number of bedrooms, the number of bathrooms, right, these are all going to be inputs. We can support how many guests, let's say max guests. That sounds like a good one.\u003C/p>\u003Cp>One of the other nice things here is the ability inside Us, if you're using it for your content management, you can add these helpful notes for your users. So how many guests total can stay at this property. And I can also translate these into other languages, which is really nice for our international users. Alright. That looks good.\u003C/p>\u003Cp>This will always be an integer. We don't support half people here at Airbnb, that's what we'll call this one. So max guess, we have number of bedrooms. Right? I can just duplicate that particular field and say bedrooms quantity, bedroom number of bedrooms.\u003C/p>\u003Cp>Like bedrooms quantity. Sounds great. I may go in and edit this individual note though. How many total bedrooms? Cool.\u003C/p>\u003Cp>We'll go through and let's do bathrooms quantity. Again, and naming is probably, I won't say it's half the battle, but it is a lot of the battle in development, is coming up with names that make sense and have meaning. So we've got our title, we've got our max guest, we've got our bedrooms, we'll have a host for that, so we'll deal with that in a moment. I can see we've got a nice description here. This looks like it supports WYSIWYG content, so we'll go ahead and add the WYSIWYG editor within Directus.\u003C/p>\u003Cp>We'll call this the Description. Could be the about section. I see about the space here. We'll save this. Great.\u003C/p>\u003Cp>And then we have this what this place offers. Right? So this is an interesting one where we've got items that are not included, we've got items that are included. There's a couple different ways we could actually manage this within our data model. We could, I like hard code this as like JSON values or something like that, where we have a list of these objects.\u003C/p>\u003Cp>We could also set it up as a separate relationship so you could add these features, or I'm assuming what we would call features to this particular listing. Maybe come back to that one. Let's keep rolling down. We have our reviews, we have the address, so we could have the street address. Let's go ahead and fill that up.\u003C/p>\u003Cp>Street address, we do this as street address 1, Street address too, just in case we need it. What else do we have? City, region, state. I think it's region if we're being international friendly outside of the US. And then we have a postal code.\u003C/p>\u003Cp>Great. Alright. Maybe we clean this up a little bit. I'm gonna make this half width. When I'm inputting these things I want to make it look really nice.\u003C/p>\u003Cp>I am going to go in and use the detail group within Directus just to get a nice visual. So we'll say detail group, let's call this Address Info, on the front end, whenever we call the API, we would either make sure we omit this address so nobody can find this property without actually booking it. We want to keep that private. Or we may not even, send that to the front end at all. But obviously you want to store that.\u003C/p>\u003Cp>And we could, you know, even potentially like obfuscate this somehow. So we've got a host. We'll come back to that. House rules. Let's take a look at this.\u003C/p>\u003Cp>Pets allowed, no smoking. Check-in, check-in, check out. Yeah. So we're gonna need details on check-in, check out. We've got safety and properties.\u003C/p>\u003Cp>We've got safety devices. We've got a cancellation policy. Before you book, partial refund. Okay. Yeah, let's tackle just like safety really quickly.\u003C/p>\u003Cp>So we'll go in. This seems something like pretty simple. Let's go in and this may be a good use case for our checkboxes inside Directus. So safety, what do they call these? Safety and property.\u003C/p>\u003Cp>Let's just call it safety devices. We'll say carbon monoxide. Then we have a smoke alarm installed. Great. And for the value, I can even change this to something like CO 2 or CO.\u003C/p>\u003Cp>Right? Carbon monoxide is single c0. Yeah. Let's just leave it there. Great.\u003C/p>\u003Cp>I'm trying to think of what other safety devices we'd have on a property. Alright? Let's just check out one of these other cabins. Scroll down to the bottom. Potential for noise, so there is some road noise.\u003C/p>\u003Cp>This is kind of like a extra metadata as well. Safety devices. Let's just finish this out. Great. We could do something interesting here where like property info is just text and an explanation.\u003C/p>\u003Cp>Right? Maybe we even have a potential icon for this. So let's use the repeater field inside Directus. Basically it just stores as JSON data, but I can create an array of objects here that can contain basically almost anything that I want. But again, it just gets stored as JSON within the database.\u003C/p>\u003Cp>So we'll call this property info, and for the fields, it looks like we've got an icon. Right, icon. We'll call the field icon, we have a string, that's the type of the field, and then we actually have an icon interface inside Directus where you can choose lovely icons from a drop down. Alright, next we'll have a label or a title for this. Let's just go with Label, that'll be a string as well.\u003C/p>\u003Cp>We'll use just a standard input interface and what's next? Right, we've got a description, label, description, close enough. And this we'll just make a text box. I don't think we need WYSIWYG input here, we'll just do text area. I said we didn't need it and then I clicked it, got a little click happy there.\u003C/p>\u003Cp>Great. Okay, so now we've got safety devices, we've got property info, we could even call this, we could add this in our detail group as well. We could call it safety property. Safety and property. Great.\u003C/p>\u003Cp>And we can even add a little safety sign. Great. Health and safety. Beautiful. So I'll just drag those within this and we are starting to build a great looking data model for this.\u003C/p>\u003Cp>Alright. Let's cover check-in, check out info. So we can use the date time field. Do we want I guess we just want time. Right?\u003C/p>\u003Cp>Check-in time. We will let's do that half width. We'll do checkout time. Cool. We've got those 2 items.\u003C/p>\u003Cp>Was there any other details on check-in? Self check-in with keypad. Maybe there's like a check-in type. Let's take a look at a couple other listings and see what we can find. Where did you go?\u003C/p>\u003Cp>There we go. Self check-in with a lockbox. Yeah. So this is probably just like some text. Check out type.\u003C/p>\u003Cp>Great. Again, if I wanted to group these together, I could add a nice little detail group. Check out, let's call it House Rules, right? House Rules. Great.\u003C/p>\u003Cp>We'll put that under house rules, check out, check-in, check out type. Okay. If I could actually click and drag the way I want. Let's, let's move that up. We've got the address info, we've got the house rules, we've got the bedrooms, max guests, pets allowed.\u003C/p>\u003Cp>That could be a Boolean attribute or a toggle here. It just stores this Boolean in the database. So our pets allowed, Default. Let's say no is the default. Pets allowed as the label.\u003C/p>\u003Cp>Great, great. Alright, we'll shrink this to half width. I'm getting really anxious to see what this actual form looks like once we get done mocking out our data model here. We've got quiet hours, right? So how do we adjust for this?\u003C/p>\u003Cp>Do we have a range of dates inside Directus? We do not. Quiet, so we could do something like quiet time start. Date time. Oh, nope.\u003C/p>\u003Cp>We just want time. Date, time, and our dates. We're gonna have to delete that one. Alright. Let's just go back.\u003C/p>\u003Cp>Let's try again. We just want the time, right. We're not really concerned with the date. Quiet time, stop quiet quiet time start. Quiet time in.\u003C/p>\u003Cp>Great. Isn't data modeling fun, guys? We'll put that in our house rules. Quiet time. Does anybody allow smoking in their properties?\u003C/p>\u003Cp>Do we even really need to concern ourselves with this? Right? Additional rules. This doesn't look like it actually supports WYSIWYG content. So we can add a new field for that.\u003C/p>\u003Cp>We'll just call it additional rules. We'll just call it a text area. That's gonna store as text in the actual database. And one of the important things to know, hey, as we're going through and modeling this data out, if I were to pull up this SQL database right now, and I could show you that in just a moment, Directus is actually going through and mirroring every change that I'm making here inside the Data Studio, inside our actual database as well. So we go to house rules, we'll just clear that up.\u003C/p>\u003Cp>Too many, There we go. Alright. I need to adjust the sensitivity on the mouse. But if I open up table plus and just to give you guys a preview of what's going on behind the scenes here. Uh-oh.\u003C/p>\u003Cp>What am I doing wrong? Connect to the database. Detour. Let's check my Docker Compose. 8 055.\u003C/p>\u003Cp>Those ports should be exposed. Oh, I see what the problem is. I've got, another instance of PostgreSQL running through Docker. All right, so if I open this up, you can see I've got a listings table here. And if I look at the structure, you can see we've got those same columns that we were mapping inside Directus, and you can see the data type is being appropriately applied.\u003C/p>\u003Cp>Likewise, if I were to add a column to the database here in TablePlus or via SQL, Directus would introspect those changes and actually show up inside the app itself. So it is a beautiful mirroring relationship. It's, I love it. It's one of my favorite features of Directus. I I know I say that a lot.\u003C/p>\u003Cp>What other things do we need to offer here? HDTV, Mountain View, Valley View. I I I don't know what this looks like behind the scenes when you're actually mapping these out. I would be curious to see. Free washer, free dryer in unit versus free washer, free dryer out of unit, outside of unit.\u003C/p>\u003Cp>Right, we've got a garden view. I'm assuming this is probably just like some type of JSON data or something that's being stored, maybe free washer end unit. Let's go back. Can we actually filter based on this data? Can you filter based on okay.\u003C/p>\u003Cp>Yeah. So so you can filter on some of this. It doesn't look like you could filter on all of it. Like, hey, does it have a in unit versus a a non unit? I'm trying to think of the best way to account for these.\u003C/p>\u003Cp>I'm not sure exactly how I want to do it. Right? So we've got this type of place that we might set up as well. Let's do that. We're gonna do the type of place and then we have our property type.\u003C/p>\u003Cp>Let's just call this one type. Okay. We'll make this a string and instead of using an input, let's use radio buttons. I think we just have a room or an entire home. Alright.\u003C/p>\u003Cp>Destinations, guests. You could filter a room or entire home, basically. Yeah. That that works for me. Alright.\u003C/p>\u003Cp>So we'll say this is a room. And one of the other cool little things that I'll show you while we're here, I can translate this content inside Directus. If I use a dollar sign t and use this key and I hit enter, I can go in and add translation strings for any of the languages, which is really nice so that, whoever is using this, Directus instance, if they are editing content and they go in and they happen to be French or Canadian. You guys speak a different language up there in Canada. Right?\u003C/p>\u003Cp>No kidding. And then we'll call this the entire home. But this translation feature is really nice. Only recently started digging into it. So the value we'll call entire home.\u003C/p>\u003Cp>And then if I want to, what I could do is go into our translations here inside the Directus Admin. I can create a custom translation, and I could say entire home, pick my English language. Great. Oh, you do have different English up there in Canada. And I could say I could call this Bryant's home if I wanted to.\u003C/p>\u003Cp>Right? It doesn't really matter, but if I were to go in and look at this data model now, right, for the type I can see it's a room or I can see it's Bryant's home. So that's really nice in that I can apply these translations for any of the languages that our our team may speak. Entire home, great. What do they say here?\u003C/p>\u003Cp>Room in a home. Alright. So if we create a new one for room, we'll go English US, a room and a home. Great. Alright.\u003C/p>\u003Cp>So we go back, we create a listing, now we can see we've got 2 options there. We've got an entire home, we've got a room and a home. Cool. Let's, let's continue finishing this up. Price ranges could be interesting, right?\u003C/p>\u003Cp>I'm trying to think of of how we would set this particular data up, because I'm imagining this varies based on, like, is it a Monday night versus a Friday night or versus a Sunday night? How many days that you're staying? Can we actually Airbnb your home? Blah blah blah. Airbnb setup.\u003C/p>\u003Cp>I guess I could log in to my account. Don't look at my passwords. Let's see what it looks like when we actually Airbnb at home. Tell us about your place. Start on your own.\u003C/p>\u003Cp>This seems to be a nice little onboarding flow as well. My house is a castle. Right? Great. Entire place, room, a shared room.\u003C/p>\u003Cp>Okay. So that's one that we forgot as well. Right? A room, and then we have shared room. So again, we'll do that dollar sign t and a colon that allows us to store that as a translatable string.\u003C/p>\u003Cp>Great. This is gonna be the entire place. Enter my address. I'm not gonna do that here. Let's do, it was Yankee Stadium.\u003C/p>\u003Cp>Right? Let's let's do the address for Yankee Stadium. Bronx. Great. Confirm the address.\u003C/p>\u003Cp>Is this PIN in the right spot? Do they have any validation on this? I guess they don't. Right? So I wonder if this goes up to, what, I don't know how many people, Yankee Stadium seats, but we'll call it 16 plus.\u003C/p>\u003Cp>Right? So we've got the number of guests, bedrooms, beds. There's something else that we need to add here. Right? Bed quantity.\u003C/p>\u003Cp>Great. Should've done this to begin with. Right? Let's add another group because this is gonna get messy. A lot of fields on this one and, you know, again, this is why I didn't want to bite off too much on this actual one.\u003C/p>\u003Cp>So we'll call this the, what do we call this? Basics listing info. Yeah. That's fine. We won't we won't get too carried away here.\u003C/p>\u003Cp>So we got the type, we got the listing info, just move that up. Max number of guests, bedrooms quantity, bathrooms quantity, are pets allowed. We'll hoist title and description up here. Let's say we've got 8 beds, 8 bedrooms, maybe 5 bedrooms. Again, no idea how many of those are inside Yankee Stadium.\u003C/p>\u003Cp>Make your place stand out. Okay. Let's look behind the scenes and see, is this making any calls here? We have parking, just using React. So I wonder Okay.\u003C/p>\u003Cp>If we amenities. Amenities. Can we see any of those here? I don't see them. I I I really don't wanna dig through each of these components here to try to figure that out.\u003C/p>\u003Cp>Can you actually do, like, the view thing in the React Dev Tools? I guess you can. Alright. So what's our consumer, provider, Value, History. Yeah.\u003C/p>\u003Cp>I'm not a not a React guy. Let's just not even bother diving through it. Right? There's our safety items, like first aid kits, smoke alarm, etcetera. Standout amenities.\u003C/p>\u003Cp>So these are just the basics, right? And you might even go through and add separate fields for for these things. So we could do again the detail group for this. Maybe this one starts closed. Call this amenities.\u003C/p>\u003Cp>These also could be like a separate database table, that we we add, and that way we could continue adding those. Yes, no, the other. Again, not knowing behind the scenes the importance of this, like maybe we add these as separate things so we could filter them. So let's go in and we'll add a new collection. We'll call it Amenities.\u003C/p>\u003Cp>Amenities. Probably need some spell checking there. Amenities. Cool. Alright.\u003C/p>\u003Cp>All of this, I'm not really concerned myself with. We have an icon for the amenity. Gotta have an icon. Very design oriented here myself. Recovering designer, as I like to refer to myself.\u003C/p>\u003Cp>We have a name for this amenity. Maybe we've got something like a key for it as well. I I I don't know. And then is there some type of explanation for these? Yeah.\u003C/p>\u003Cp>You may have something like a description for it as well. Cool. Good enough. Now what we're gonna do is link these 2 together, right? So inside Directus what I can do is basically create a many to many relationship, because one property could have many amenities and many amenities could be linked to many properties.\u003C/p>\u003Cp>I think that's a great explanation of amenities and many to many relationships. Directus makes this super simple to create these relationships. So on our listings collection, we'll just call this Amin I need to work on my spelling for sure. My wife works in the school system as well, so maybe she can give me some help. We don't wanna allow duplicates.\u003C/p>\u003Cp>We wanna to show a link to that specific item. If I open up advanced field creation mode I can actually control the name of this junction collection. That's going to create a junction table in our SQL database. I'm okay with this, right? And if I wanted to I could add a reverse many to many relationship, on the amenities collection back to the listings.\u003C/p>\u003Cp>What we may do is add a sort field here as well. For the display, we'll show related values. We can come back in and clean that up in just a moment. But, great, we've got our amenities here. Let's move that up to the top.\u003C/p>\u003Cp>What are the other things that we need to add? Some photos of the castle. You need 5 photos to get started. I'm feeling pretty confident here. I'm just going to close this out.\u003C/p>\u003Cp>Exit. We'll go back to the Airbnb website. And there was one thing that I forgot what I was gonna add here. Is, property type. That's what it was.\u003C/p>\u003Cp>So we'll go in, I'm just gonna duplicate this one called type and we'll add property type. Duplicate, that's of course gonna drop down here to the bottom and I'll just bring it back up. Property type, let's just clear out these values. So it probably didn't need to duplicate that. But we have a house.\u003C/p>\u003Cp>I'm not gonna mess with the translation strings here. Just know that's available to you if you are working with Directus. We'll call this an apartment. Apartment. Guest house.\u003C/p>\u003Cp>For those of you who do have a guest house, amazing. Take advantage of it. Exploit it on Airbnb. We have the hotel and that looks pretty good. Cool.\u003C/p>\u003Cp>At some point we'll probably have to deal with the pricing thing, but for now we'll just skip it. Alright, so I'm feeling pretty confident about this. Let's go in and flesh out the other items in our data model. We spend a ton of time data modeling this out, but there's a lot of fields on the listings. So we got reviews, we have tags.\u003C/p>\u003Cp>So let's create reviews. What does the review structure look like inside Airbnb? So we're certainly gonna want a user created. We'll probably have a status on the review. Hey, is this published or not?\u003C/p>\u003Cp>For the reviews, okay. It looks like we have an overall rating. We have cleanliness, accuracy, location, value. Alright. So I think we have a slider field that it could be a good representation for this.\u003C/p>\u003Cp>Let's call it overall rating. Great. We'll duplicate that one. We can call it cleanliness. Cleanliness?\u003C/p>\u003Cp>Yeah, I think so. Looks great. Let's duplicate this. We'll have the accuracy and so forth. I just missed that one, didn't I?\u003C/p>\u003Cp>Duplicate. Alright. So you kind of get the picture there. We have a user, we have some actual text. It doesn't look like, to me that doesn't look like WYSIWYG text.\u003C/p>\u003Cp>Not that it matters. The WYSIWYG and the text area interface both store the data as text inside the database. So we'll call this, what, Content? This is our message. And I'm not sure if this is like other platforms where you get a response as well, where if somebody leaves you a crappy review, you can respond to it.\u003C/p>\u003Cp>So we got reviews. Let's link those to our listings. Alright. Again, that's gonna be a many to many relationship. So we'll say Reviews, we'll do Reviews, all this is gonna be happening for us.\u003C/p>\u003Cp>One thing that I see a lot of people miss is this sort field on the relationship page, so you can actually sort these items in the order that you want. Probably not necessary for reviews because you're just going to, do those probably based on recency or or the actual date. What else do we have? In our original model, we had host profile, we had our reviews, we had our tags. Let's go in and add tags.\u003C/p>\u003Cp>Great. Not not really concerned with any of this. We'll just call it a tag name. Okay. And again, we're going to be using our many to many relationship.\u003C/p>\u003Cp>Because we could have many tags on many different listings. Sweet. Great. Anything else that we need? Let's do a host or hosts as we'll call them, or we could call this host profiles maybe, because they're they're we're gonna link these back to the Directus users collection.\u003C/p>\u003Cp>Again, just so we're not gumming up or you know, making a mess out of the system table. But behind the scenes, if I pull back up my database, you'll see that all of your SQL data here, all your data in your SQL database, it remains purer because all of the metadata that Directus uses, it stores in its own name space tables. The one that kind of sits in the middle of that is Directus Users. This is what you get out of the box. You get the authentication, we have the permissions that are tied to this, so you get rule based access control, which is really nice.\u003C/p>\u003Cp>Right? You get all of this out of the box. I hate to junk up my system tables, though. So what I usually do in situations like this is just add a link back to those. So instead of a mini to mini, we'll just do like a mini to 1.\u003C/p>\u003Cp>And we'll call this our user. For the related collection it's gonna be directus underscore users. And I'm gonna go into field advanced creation mode, we'll call this host profile. And one other thing I'm gonna do is make this value unique. Great.\u003C/p>\u003Cp>Okay. So now we have our host profile. What does this look like? Santa is our host. Do we have any other details?\u003C/p>\u003Cp>Let's pull somebody else up. I'll click the same one. See what kind of data we're storing on the host profile. Alright. Probably an image on the host profile.\u003C/p>\u003Cp>Great. So we'll just add an image, or we could call this Avatar. And how are we doing on time? Looks like we've got roughly like 15 minutes left. We're doing a lot of data modeling on this.\u003C/p>\u003Cp>We've got our user avatar. We've got a description. Great. Alright. Let's go in and we've got the details for that.\u003C/p>\u003Cp>Let's add our host to the listing as well. So a listing belongs to a single host. I see we've got co hosts here, but, not super concerned with that. That's gonna be our many to one collection, or relationship. So a host owns a single property or a host is responsible for a single property, but a host could have many properties.\u003C/p>\u003Cp>So we'll choose our host profile. Great. Save it. Okay. Alright, so let's go in and look at this giant form that we're gonna add all of our data.\u003C/p>\u003Cp>Right, now the really cool part here is I've modeled all this data out, and I'll show you in a moment. I'm just gonna copy and paste basically everything from this. If I was smart I probably would have built like a scraper or something to input all this data. Cool. There we go.\u003C/p>\u003Cp>We've got the description when it comes time to the amenities. Let's just go in and create a new amenity. We've got mountains, view. Yeah, that kinda looks like a mountain view. Mountain view.\u003C/p>\u003Cp>We could give it a key. That could be helpful if we're actually filtering this on the front end. Dedicated workspace. Alright. So I can go through and do a desk icon.\u003C/p>\u003Cp>Not quite as fancy as what they have here. We'll just use Workspace as the key. Cool. Great. So we added some amenities.\u003C/p>\u003Cp>This is what? This is a entire cabin? Yeah. This looks like a house. This is an entire home.\u003C/p>\u003Cp>We have up to 4 guests. You can see we've got that nice little note that we had here, so this is just providing that extra little for our user experience. We've got 1 bedroom, 2 beds, 1 bath. Are pets allowed? Do we see pets on this one?\u003C/p>\u003Cp>Let's say no. Right? Check-in time, where do we find that? We've got our check-in time is 4 pm. 4 PM.\u003C/p>\u003Cp>Check out time, 10 AM. K. Check out type. Self check-in with a lockbox. Do we have a quiet time?\u003C/p>\u003Cp>I don't see quiet time. I'm gonna set quiet time. Right? From midnight to 5 AM? I don't remember.\u003C/p>\u003Cp>Great. Additional rules, we can see outside guests, no allowed parties, lots of additional rules at this particular place. We've got the address, I'm not even going to bother filling that out. We'll add some property info. What do we have there?\u003C/p>\u003Cp>Must Climb Stairs. That's a great one, right? Must climb stairs. You have to be able to climb some stairs to access the property. Stairs.\u003C/p>\u003Cp>We even have a nice stairs icon. Great. We don't have any reviews. We're gonna tag this as a cabin in the woods. And then we have our host.\u003C/p>\u003Cp>Right? So we'll just create a new host profile. Who is our host? Sierra. I I'm not gonna do that.\u003C/p>\u003Cp>Let's just use, like, user avatar, sample user avatar. Yeah. This guy looks great. Kinda like Tim Cook or something. Copy image address, upload, we'll call this guy test host, and save.\u003C/p>\u003Cp>Alright. So now we've got a listing. The only thing we're actually missing here is some images. Right? So I can go back into our listing, I can hit create, and here we can add files to this.\u003C/p>\u003Cp>And files could be images, we're just gonna call it images, that's great. We can even choose what folder we want to upload these things to. So we'll save that, drag it back to the top, add some images for this, and where'd you go? I'm just gonna pick one of these. One of the things that I really like is, it's super handy to be able to upload images from a URL here without having to download them.\u003C/p>\u003Cp>Not sure about you, but I usually get like a 1,000,000 1,000,000 files on my desktop that, you know, just clutter up things after a while. Okay. Alright. So now we've created this listing, right? And, if I pull up our application, we got this really nice Nuxt application.\u003C/p>\u003Cp>I'm gonna just move Airbnb out of my way as well. But if I pull up this application Oh, I closed Directus. Let me bring that back. I still haven't mastered Arc. I'm not sure if you guys are Arc users or not, but, it can be a challenge.\u003C/p>\u003Cp>Alright. So we've got Directus, we've got our local host. I'm gonna pull up our code base. This is just a senior Nuxt application. I've already got an SDK configured for this.\u003C/p>\u003Cp>And if I look, I've got an extra leftover page here, but we've got this index page, we've got 100 apps, 100 hours. If I wanted to just use like the async data call from NUXT. So if I look at the NUXT documentation, they have a composable in here, use async Data for Data Fetching. And this takes care of things like caching, refreshing, it gives you like pending state, things like that that you can use. So I could potentially just copy this here.\u003C/p>\u003Cp>We've got Use Async Data. Let's call this Listings, so we give it a key. This could be like the actual route, like the full path of the route. But here, I'm going to go in and we are going to do something like this, where we say return use directus. I've got a composable here and I'm going to read items from the listings collection.\u003C/p>\u003Cp>The next thing I would add here could be, you see Copilot is already offering some suggestions of hey, only show me published listings or like the specific fields that I want. Let's not even bother with that at the moment. I may just leave that on there, though. Great. Do we have everything we need?\u003C/p>\u003Cp>Refresh interval. Where am I missing? I don't need refresh interval. Don't really want refresh interval. What does our parser say?\u003C/p>\u003Cp>Do I have too many of these? Let's just keep it simple. Oh, yeah, missing the last one. But there's that's what I was missing. Okay.\u003C/p>\u003Cp>So now I'm just gonna log this out. So if I do data, maybe we wrap it in a pre tag just to see what that looks like. Are we getting anything back from Directus? No. Why not?\u003C/p>\u003Cp>Subscripts. Parse error. Use async data listings. We're going to return use directus read items. That should be what I've got set up here.\u003C/p>\u003Cp>Use directus. Use directus rest. No. It should just be oh. Do we need to return a wait?\u003C/p>\u003Cp>Nope. What is the problemo? Let's actually just ditch async data for a moment. Const data equals use await. Use directus, read items listings.\u003C/p>\u003Cp>Alright. And this should do the fetching here. And now I can see the problem. Nuxt has SSR configured out of the gate, so I wasn't seeing those errors here on the client side. But basically this is showing 403 forbidden.\u003C/p>\u003Cp>And the reason why is because I haven't set up my permissions inside Directus. So if I were to just log that back, I could go into Directus and under our rule based access control, I'll just go in and for now, and because we're running out of time here anyway, I'm just gonna set this to public. All this data that we set up for the individual listings, all of our tags. One of the things that I could do here, right, this is public data, so I don't want to maybe I want to show the city and like the state or the country, but I can go in and I can set custom permission settings. So where I have the, you know, I could go in and say okay, only items that are published will be shown and are available to the public, so without login.\u003C/p>\u003Cp>But things like street address and postal code, you know, if I was storing data like phone numbers or email addresses, right, I wouldn't want to make that data available to the public. You know, the rest of this data, hey, they need to know and we probably still want to surface that if they are, just browsing the site and they're not logged in. But for the actual sensitive data, I don't want to I don't want to give that up. Alright. We've saved our Let's just take a look and see what we get back here.\u003C/p>\u003Cp>Okay. So I can see the listings. Right? We're not getting any data. And again, I thought I was using async data wrong here.\u003C/p>\u003Cp>I need to brush up on its use, but the status of this post is not published. So now if I go in and publish it, because we set up that rule based access control, Now we can see we got some data. It's rendering kind of funky because I'm flexing it. Okay. Alright.\u003C/p>\u003Cp>So now we can see we're actually fetching all the data from our API. So which is really nice. Directus gives us these REST APIs. As soon as we set up our data model, we can see all this information inside here. All right.\u003C/p>\u003Cp>So if I look at the clock, we've got, just a few moments left. Let's go in and just try to flesh something out as far as a detail page. If I look, I've got some images here. How do I actually get access to this relational data? One of the nice things inside Directus is the ability to pass a fields array that will actually act as a GraphQL API.\u003C/p>\u003Cp>So you get all the benefits of GraphQL, and then I can specify all the fields that I want to return, and I can even grab relational data, as deep as I want to go, which you have to be very careful about recursive relationships. But within that, I can go and grab all of that. So if I wanted to grab the root level fields, I could. I could also do something like this where I say images.asterisk, that will give me the Directus Files ID that I would need to render these types on, on the actual on the site. Yeah.\u003C/p>\u003Cp>Simple enough. Alright. Let's flush this out as quickly as possible. Alright. So I'm gonna clean that up.\u003C/p>\u003Cp>What do we have here? Underlying this, I've been using the Nuxt UI library, but I'm always a huge fan of Tailwind UI. If you don't have it, sign up for it. It's a great tool. All right.\u003C/p>\u003Cp>This is probably, could be similar to a product page, maybe. Alright. Let's take a look. Product overviews. Got a couple of these.\u003C/p>\u003Cp>I really like this image grid component. Image gallery. Let's just lift this wholesale. Oh, the Linter is doing a number on this thing, right? So here we go back.\u003C/p>\u003Cp>We're probably getting an error somewhere. The View Dev Tools and the Nuxt Dev Tools, I can't recommend enough. They're very great. But if I go in and I look for my index page, I can see our data that we've got here. And maybe here, I actually want to, is there where's the key?\u003C/p>\u003Cp>I think I actually do something like this where I transform and I do data, and I do data dot 0. Is that valid? No. Yeah. Maybe that's right.\u003C/p>\u003Cp>No overload. Not just this call Because we've got the return. Oh, no. I think it needs to be one more out here. Okay.\u003C/p>\u003Cp>Now if I look, data transform. Product dot images. Okay. So this is gonna be data dot images. So if we just clean these up.\u003C/p>\u003Cp>Data data dot images. Oh, missed one. We'll just clean that up. Data dot images dot source. Yeah.\u003C/p>\u003Cp>So we're not getting source here. Are we getting any actual data at all? Right? It's not showing me anything in the view dev tools. This could be a case of where I spent way too much time data modeling, without actually building anything.\u003C/p>\u003Cp>But at least, you will have a good idea of the back end structure. 0 of undefined. Let's just, comment this out. Get our data back. Data.\u003C/p>\u003Cp>Pre. Alright. So we have data dot images. Now I gotta do data dot 1. Let's just do this.\u003C/p>\u003Cp>Our listing equals data, data dot value, and we'll log our listing. Okay. And now I should be able to do something like this where, I've got Nuxt Image connected to this as well, which is a great tool. So we use Nuxt Image and in the actual config for Nuxt you can set up the provider for Directus because we use the same underlying library. Where are you?\u003C/p>\u003Cp>And then I can set the base URL for my assets. So then I don't even have to actually worry about passing the URL. So Nuxt data dot images. Is it gonna be data? It's listing dot images, and then where it says source, we're actually gonna grab this directus file's ID.\u003C/p>\u003Cp>Underscore files underscore ID. Do we actually get images here? If I remove the alt text, does this actually work? Am I are are we kidding, man? Why do I not have this?\u003C/p>\u003Cp>Right? If I close this out, why is it not showing? Images, directus files underscore ID. Yeah. Alright, well regardless we hit the timer.\u003C/p>\u003Cp>I didn't get very far on the front end on this one. I chalk it up to spending too much time on the back end, but oh, what a back end it is. It's very beautiful. We've got a detailed data model that, we've got our REST API for, if you prefer GraphQL, you could query this on the front end to your heart's desire. Again, maybe I was rightfully a little scared of this Airbnb just because of everything that is involved in all the details on this.\u003C/p>\u003Cp>Anyway, I had a lot of fun with this one. I hope you enjoyed it. Stay tuned for the next episode. Got a great one coming. I'll see you.\u003C/p>","Alright. Alright. Alright. Welcome back to the next episode of 100 Apps, 100 Hours, where we build or rebuild some of your favorite apps, or publicly fail trying. I'm your host Brian Gillespie, developer advocate at Directus. And today we have the mother of all clones, Airbnb. This was actually going to be one of the first episodes that I was going to record for this series. To be honest, I got a little scared. Airbnb has a ton of functionality on the front end. We have authentication, we have listings, you have 2 different types of users, and that you have folks who are listing their properties, folks who are booking those properties, you have messages, you have reviews. There's a ton of things just underneath the surface. So I'm curious to see how far we can get in an hour. I don't want to bite off more than I could chew, but, should be entertaining nonetheless. Alright. Alright. So if you've tuned into the series before, you'll know we have two rules. The first is you have 60 minutes to plan and build, or I have 60 minutes to plan and build, if you've got 60 minutes, if you're watching along, maybe you want to build with me. It's great. No more, no less, that's all we get. And then the second rule is kind of the anti rule. Use whatever you have at your disposal. AI, chat GPT, I've got a Nuxt front end application starter set up that already has some plumbing to connect to my Directus Instance, and I have a blank Directus project. That's what I've got. We're gonna try to build this Airbnb clone. Let's get started. Alright, so we'll start the clock here and away we go, right? So I'm going to pull up the Airbnb website, if I can actually figure out how to work my computer, and let's just poke around and see what we can find, right? One of the first things that I usually like to do when I'm trying to break down functionality for an app is take a look at the navigation, right? Airbnb has this concept of stays versus experiences now, so we have properties or listings or something like that. We have experiences which are, tours and tastings and and actual things that you can do. So that should be an interesting dynamic. I I think we'll mostly probably just stick to the properties part of it, like the listings. You know, one of the other things that I always like to take a look at, you know, I can go in and check the network requests, you know, and and try to suss out what some of the the data model looks like for all these different things that we're querying on the front end. So it looks like there is a very, very interesting, set of queries they're using. If I look into, like, the actual property values, we've got this very detailed data that we're returning and using potentially for our layout. I don't really know how helpful this is going to be, right? And to my knowledge, I don't know that Airtable, or I'm sorry, Airbnb has a public API that I can use to kind of introspect the schema, so basically I'm just playing it by ear here, right? So on every episode, before I actually build, I like to do a bit of planning. And first, let's start with like what type of functionality we're going to try to achieve out of this. I certainly want to get the back end data model built for this, we'll map that out in just a moment. On the front end maybe we want to display a listing detail page and maybe a listing index page. Let's call that our original goal. If we got like a stretch goal, maybe we want to let folks book booking pages, booking properties, sending messages, something like that. So I think these three pieces here will be plenty to chew on for an hour. Let's, let's try to map this application out. And I really like Figma, FigJam, the combo is great for mapping out things like this. We'll just do no fill. Alright, so as far as our data model, naming things is always tricky. So are we gonna call this Listings, is it Properties? That's gonna be our main collection, right, this is our main table. Not a 100% sold on what it's gonna be yet. We've probably got users in the system, so we'll have our users and Directus is going to actually take care of that for us with the Directus users collection that that it bootstraps whenever we actually create our instance. What else do we have? We've got, reviews that are going to be included and it can be attached to the individual listings. Looks like we have categories as well. Right? Tiny Homes, Countryside, I don't know if those are gonna be tags. I guess you could be a Countryside and a Tiny Home. So that could be like a mini to mini relationship. So let's just call those tags. Great. We could tag those onto a property. We have bookings. Alright. So you've got a calendar of bookings for a property or listing. We can make those little relationships. Just draw some nice looking arrows, basically. That's all we're doing here. Then we've probably got a junction table for that mini to mini relationship. Cool. Just drag that out of the way. We've got our reviews. Tags. Okay. Listing tags. Alright. What else? If we open up filters, we could see things like, the type of place, the price range, bedrooms, number of bedrooms, property type, the amenities. That's kind of a nice bit of functionality. How are we going to deal with that? What are the booking options? Host language. If we go to the actual detail page for a property, right, we've got a gallery of multiple images, we've got a host that could be our user, I guess. You know, we might have a like a host profile or something like that, that we attach so that we don't pollute our users collection. We've got a schedule, we've got the reviews, we've got a map, yeah. Okay. I feel pretty good about this. Let's just draw a couple more arrows, just for the sake of arrows. Let's actually start building this out. Cool. Alright, so I'm going to drag this over and as you can see over here on the left I've got my blank instance of Directus. What are we gonna call this main one? Properties to me as a developer, I'm thinking like properties of an object or something like that, so I'm I'm gonna call this listings. If anybody from Airbnb is watching this, hey, maybe tell me what you're what kind of schema you're using on the back end. All right, so I'm going to create a new collection called Listings. We're going to use the generated UUID. Is this a published listing? Yes, probably. So we'll keep status, we'll have the created on, updated on, user created, etcetera. And let's go through and start modeling this out. Alright. So if I look, we've got a h one, this is probably just the title of the listing. Great. What happens to our URL? It looks like they don't use fancy URLs or like a slug for this listing. It's just actually by the ID. And it looks like it's rooms. I could have just looked at the URL. I kinda like listings better just because they are using, they're listing whole houses on here as well, right, not just a room. So, cool. We will have a, this looks like it's calculated. What are the other things that we need? The number of guests, the number of bedrooms, the number of bathrooms, right, these are all going to be inputs. We can support how many guests, let's say max guests. That sounds like a good one. One of the other nice things here is the ability inside Us, if you're using it for your content management, you can add these helpful notes for your users. So how many guests total can stay at this property. And I can also translate these into other languages, which is really nice for our international users. Alright. That looks good. This will always be an integer. We don't support half people here at Airbnb, that's what we'll call this one. So max guess, we have number of bedrooms. Right? I can just duplicate that particular field and say bedrooms quantity, bedroom number of bedrooms. Like bedrooms quantity. Sounds great. I may go in and edit this individual note though. How many total bedrooms? Cool. We'll go through and let's do bathrooms quantity. Again, and naming is probably, I won't say it's half the battle, but it is a lot of the battle in development, is coming up with names that make sense and have meaning. So we've got our title, we've got our max guest, we've got our bedrooms, we'll have a host for that, so we'll deal with that in a moment. I can see we've got a nice description here. This looks like it supports WYSIWYG content, so we'll go ahead and add the WYSIWYG editor within Directus. We'll call this the Description. Could be the about section. I see about the space here. We'll save this. Great. And then we have this what this place offers. Right? So this is an interesting one where we've got items that are not included, we've got items that are included. There's a couple different ways we could actually manage this within our data model. We could, I like hard code this as like JSON values or something like that, where we have a list of these objects. We could also set it up as a separate relationship so you could add these features, or I'm assuming what we would call features to this particular listing. Maybe come back to that one. Let's keep rolling down. We have our reviews, we have the address, so we could have the street address. Let's go ahead and fill that up. Street address, we do this as street address 1, Street address too, just in case we need it. What else do we have? City, region, state. I think it's region if we're being international friendly outside of the US. And then we have a postal code. Great. Alright. Maybe we clean this up a little bit. I'm gonna make this half width. When I'm inputting these things I want to make it look really nice. I am going to go in and use the detail group within Directus just to get a nice visual. So we'll say detail group, let's call this Address Info, on the front end, whenever we call the API, we would either make sure we omit this address so nobody can find this property without actually booking it. We want to keep that private. Or we may not even, send that to the front end at all. But obviously you want to store that. And we could, you know, even potentially like obfuscate this somehow. So we've got a host. We'll come back to that. House rules. Let's take a look at this. Pets allowed, no smoking. Check-in, check-in, check out. Yeah. So we're gonna need details on check-in, check out. We've got safety and properties. We've got safety devices. We've got a cancellation policy. Before you book, partial refund. Okay. Yeah, let's tackle just like safety really quickly. So we'll go in. This seems something like pretty simple. Let's go in and this may be a good use case for our checkboxes inside Directus. So safety, what do they call these? Safety and property. Let's just call it safety devices. We'll say carbon monoxide. Then we have a smoke alarm installed. Great. And for the value, I can even change this to something like CO 2 or CO. Right? Carbon monoxide is single c0. Yeah. Let's just leave it there. Great. I'm trying to think of what other safety devices we'd have on a property. Alright? Let's just check out one of these other cabins. Scroll down to the bottom. Potential for noise, so there is some road noise. This is kind of like a extra metadata as well. Safety devices. Let's just finish this out. Great. We could do something interesting here where like property info is just text and an explanation. Right? Maybe we even have a potential icon for this. So let's use the repeater field inside Directus. Basically it just stores as JSON data, but I can create an array of objects here that can contain basically almost anything that I want. But again, it just gets stored as JSON within the database. So we'll call this property info, and for the fields, it looks like we've got an icon. Right, icon. We'll call the field icon, we have a string, that's the type of the field, and then we actually have an icon interface inside Directus where you can choose lovely icons from a drop down. Alright, next we'll have a label or a title for this. Let's just go with Label, that'll be a string as well. We'll use just a standard input interface and what's next? Right, we've got a description, label, description, close enough. And this we'll just make a text box. I don't think we need WYSIWYG input here, we'll just do text area. I said we didn't need it and then I clicked it, got a little click happy there. Great. Okay, so now we've got safety devices, we've got property info, we could even call this, we could add this in our detail group as well. We could call it safety property. Safety and property. Great. And we can even add a little safety sign. Great. Health and safety. Beautiful. So I'll just drag those within this and we are starting to build a great looking data model for this. Alright. Let's cover check-in, check out info. So we can use the date time field. Do we want I guess we just want time. Right? Check-in time. We will let's do that half width. We'll do checkout time. Cool. We've got those 2 items. Was there any other details on check-in? Self check-in with keypad. Maybe there's like a check-in type. Let's take a look at a couple other listings and see what we can find. Where did you go? There we go. Self check-in with a lockbox. Yeah. So this is probably just like some text. Check out type. Great. Again, if I wanted to group these together, I could add a nice little detail group. Check out, let's call it House Rules, right? House Rules. Great. We'll put that under house rules, check out, check-in, check out type. Okay. If I could actually click and drag the way I want. Let's, let's move that up. We've got the address info, we've got the house rules, we've got the bedrooms, max guests, pets allowed. That could be a Boolean attribute or a toggle here. It just stores this Boolean in the database. So our pets allowed, Default. Let's say no is the default. Pets allowed as the label. Great, great. Alright, we'll shrink this to half width. I'm getting really anxious to see what this actual form looks like once we get done mocking out our data model here. We've got quiet hours, right? So how do we adjust for this? Do we have a range of dates inside Directus? We do not. Quiet, so we could do something like quiet time start. Date time. Oh, nope. We just want time. Date, time, and our dates. We're gonna have to delete that one. Alright. Let's just go back. Let's try again. We just want the time, right. We're not really concerned with the date. Quiet time, stop quiet quiet time start. Quiet time in. Great. Isn't data modeling fun, guys? We'll put that in our house rules. Quiet time. Does anybody allow smoking in their properties? Do we even really need to concern ourselves with this? Right? Additional rules. This doesn't look like it actually supports WYSIWYG content. So we can add a new field for that. We'll just call it additional rules. We'll just call it a text area. That's gonna store as text in the actual database. And one of the important things to know, hey, as we're going through and modeling this data out, if I were to pull up this SQL database right now, and I could show you that in just a moment, Directus is actually going through and mirroring every change that I'm making here inside the Data Studio, inside our actual database as well. So we go to house rules, we'll just clear that up. Too many, There we go. Alright. I need to adjust the sensitivity on the mouse. But if I open up table plus and just to give you guys a preview of what's going on behind the scenes here. Uh-oh. What am I doing wrong? Connect to the database. Detour. Let's check my Docker Compose. 8 055. Those ports should be exposed. Oh, I see what the problem is. I've got, another instance of PostgreSQL running through Docker. All right, so if I open this up, you can see I've got a listings table here. And if I look at the structure, you can see we've got those same columns that we were mapping inside Directus, and you can see the data type is being appropriately applied. Likewise, if I were to add a column to the database here in TablePlus or via SQL, Directus would introspect those changes and actually show up inside the app itself. So it is a beautiful mirroring relationship. It's, I love it. It's one of my favorite features of Directus. I I know I say that a lot. What other things do we need to offer here? HDTV, Mountain View, Valley View. I I I don't know what this looks like behind the scenes when you're actually mapping these out. I would be curious to see. Free washer, free dryer in unit versus free washer, free dryer out of unit, outside of unit. Right, we've got a garden view. I'm assuming this is probably just like some type of JSON data or something that's being stored, maybe free washer end unit. Let's go back. Can we actually filter based on this data? Can you filter based on okay. Yeah. So so you can filter on some of this. It doesn't look like you could filter on all of it. Like, hey, does it have a in unit versus a a non unit? I'm trying to think of the best way to account for these. I'm not sure exactly how I want to do it. Right? So we've got this type of place that we might set up as well. Let's do that. We're gonna do the type of place and then we have our property type. Let's just call this one type. Okay. We'll make this a string and instead of using an input, let's use radio buttons. I think we just have a room or an entire home. Alright. Destinations, guests. You could filter a room or entire home, basically. Yeah. That that works for me. Alright. So we'll say this is a room. And one of the other cool little things that I'll show you while we're here, I can translate this content inside Directus. If I use a dollar sign t and use this key and I hit enter, I can go in and add translation strings for any of the languages, which is really nice so that, whoever is using this, Directus instance, if they are editing content and they go in and they happen to be French or Canadian. You guys speak a different language up there in Canada. Right? No kidding. And then we'll call this the entire home. But this translation feature is really nice. Only recently started digging into it. So the value we'll call entire home. And then if I want to, what I could do is go into our translations here inside the Directus Admin. I can create a custom translation, and I could say entire home, pick my English language. Great. Oh, you do have different English up there in Canada. And I could say I could call this Bryant's home if I wanted to. Right? It doesn't really matter, but if I were to go in and look at this data model now, right, for the type I can see it's a room or I can see it's Bryant's home. So that's really nice in that I can apply these translations for any of the languages that our our team may speak. Entire home, great. What do they say here? Room in a home. Alright. So if we create a new one for room, we'll go English US, a room and a home. Great. Alright. So we go back, we create a listing, now we can see we've got 2 options there. We've got an entire home, we've got a room and a home. Cool. Let's, let's continue finishing this up. Price ranges could be interesting, right? I'm trying to think of of how we would set this particular data up, because I'm imagining this varies based on, like, is it a Monday night versus a Friday night or versus a Sunday night? How many days that you're staying? Can we actually Airbnb your home? Blah blah blah. Airbnb setup. I guess I could log in to my account. Don't look at my passwords. Let's see what it looks like when we actually Airbnb at home. Tell us about your place. Start on your own. This seems to be a nice little onboarding flow as well. My house is a castle. Right? Great. Entire place, room, a shared room. Okay. So that's one that we forgot as well. Right? A room, and then we have shared room. So again, we'll do that dollar sign t and a colon that allows us to store that as a translatable string. Great. This is gonna be the entire place. Enter my address. I'm not gonna do that here. Let's do, it was Yankee Stadium. Right? Let's let's do the address for Yankee Stadium. Bronx. Great. Confirm the address. Is this PIN in the right spot? Do they have any validation on this? I guess they don't. Right? So I wonder if this goes up to, what, I don't know how many people, Yankee Stadium seats, but we'll call it 16 plus. Right? So we've got the number of guests, bedrooms, beds. There's something else that we need to add here. Right? Bed quantity. Great. Should've done this to begin with. Right? Let's add another group because this is gonna get messy. A lot of fields on this one and, you know, again, this is why I didn't want to bite off too much on this actual one. So we'll call this the, what do we call this? Basics listing info. Yeah. That's fine. We won't we won't get too carried away here. So we got the type, we got the listing info, just move that up. Max number of guests, bedrooms quantity, bathrooms quantity, are pets allowed. We'll hoist title and description up here. Let's say we've got 8 beds, 8 bedrooms, maybe 5 bedrooms. Again, no idea how many of those are inside Yankee Stadium. Make your place stand out. Okay. Let's look behind the scenes and see, is this making any calls here? We have parking, just using React. So I wonder Okay. If we amenities. Amenities. Can we see any of those here? I don't see them. I I I really don't wanna dig through each of these components here to try to figure that out. Can you actually do, like, the view thing in the React Dev Tools? I guess you can. Alright. So what's our consumer, provider, Value, History. Yeah. I'm not a not a React guy. Let's just not even bother diving through it. Right? There's our safety items, like first aid kits, smoke alarm, etcetera. Standout amenities. So these are just the basics, right? And you might even go through and add separate fields for for these things. So we could do again the detail group for this. Maybe this one starts closed. Call this amenities. These also could be like a separate database table, that we we add, and that way we could continue adding those. Yes, no, the other. Again, not knowing behind the scenes the importance of this, like maybe we add these as separate things so we could filter them. So let's go in and we'll add a new collection. We'll call it Amenities. Amenities. Probably need some spell checking there. Amenities. Cool. Alright. All of this, I'm not really concerned myself with. We have an icon for the amenity. Gotta have an icon. Very design oriented here myself. Recovering designer, as I like to refer to myself. We have a name for this amenity. Maybe we've got something like a key for it as well. I I I don't know. And then is there some type of explanation for these? Yeah. You may have something like a description for it as well. Cool. Good enough. Now what we're gonna do is link these 2 together, right? So inside Directus what I can do is basically create a many to many relationship, because one property could have many amenities and many amenities could be linked to many properties. I think that's a great explanation of amenities and many to many relationships. Directus makes this super simple to create these relationships. So on our listings collection, we'll just call this Amin I need to work on my spelling for sure. My wife works in the school system as well, so maybe she can give me some help. We don't wanna allow duplicates. We wanna to show a link to that specific item. If I open up advanced field creation mode I can actually control the name of this junction collection. That's going to create a junction table in our SQL database. I'm okay with this, right? And if I wanted to I could add a reverse many to many relationship, on the amenities collection back to the listings. What we may do is add a sort field here as well. For the display, we'll show related values. We can come back in and clean that up in just a moment. But, great, we've got our amenities here. Let's move that up to the top. What are the other things that we need to add? Some photos of the castle. You need 5 photos to get started. I'm feeling pretty confident here. I'm just going to close this out. Exit. We'll go back to the Airbnb website. And there was one thing that I forgot what I was gonna add here. Is, property type. That's what it was. So we'll go in, I'm just gonna duplicate this one called type and we'll add property type. Duplicate, that's of course gonna drop down here to the bottom and I'll just bring it back up. Property type, let's just clear out these values. So it probably didn't need to duplicate that. But we have a house. I'm not gonna mess with the translation strings here. Just know that's available to you if you are working with Directus. We'll call this an apartment. Apartment. Guest house. For those of you who do have a guest house, amazing. Take advantage of it. Exploit it on Airbnb. We have the hotel and that looks pretty good. Cool. At some point we'll probably have to deal with the pricing thing, but for now we'll just skip it. Alright, so I'm feeling pretty confident about this. Let's go in and flesh out the other items in our data model. We spend a ton of time data modeling this out, but there's a lot of fields on the listings. So we got reviews, we have tags. So let's create reviews. What does the review structure look like inside Airbnb? So we're certainly gonna want a user created. We'll probably have a status on the review. Hey, is this published or not? For the reviews, okay. It looks like we have an overall rating. We have cleanliness, accuracy, location, value. Alright. So I think we have a slider field that it could be a good representation for this. Let's call it overall rating. Great. We'll duplicate that one. We can call it cleanliness. Cleanliness? Yeah, I think so. Looks great. Let's duplicate this. We'll have the accuracy and so forth. I just missed that one, didn't I? Duplicate. Alright. So you kind of get the picture there. We have a user, we have some actual text. It doesn't look like, to me that doesn't look like WYSIWYG text. Not that it matters. The WYSIWYG and the text area interface both store the data as text inside the database. So we'll call this, what, Content? This is our message. And I'm not sure if this is like other platforms where you get a response as well, where if somebody leaves you a crappy review, you can respond to it. So we got reviews. Let's link those to our listings. Alright. Again, that's gonna be a many to many relationship. So we'll say Reviews, we'll do Reviews, all this is gonna be happening for us. One thing that I see a lot of people miss is this sort field on the relationship page, so you can actually sort these items in the order that you want. Probably not necessary for reviews because you're just going to, do those probably based on recency or or the actual date. What else do we have? In our original model, we had host profile, we had our reviews, we had our tags. Let's go in and add tags. Great. Not not really concerned with any of this. We'll just call it a tag name. Okay. And again, we're going to be using our many to many relationship. Because we could have many tags on many different listings. Sweet. Great. Anything else that we need? Let's do a host or hosts as we'll call them, or we could call this host profiles maybe, because they're they're we're gonna link these back to the Directus users collection. Again, just so we're not gumming up or you know, making a mess out of the system table. But behind the scenes, if I pull back up my database, you'll see that all of your SQL data here, all your data in your SQL database, it remains purer because all of the metadata that Directus uses, it stores in its own name space tables. The one that kind of sits in the middle of that is Directus Users. This is what you get out of the box. You get the authentication, we have the permissions that are tied to this, so you get rule based access control, which is really nice. Right? You get all of this out of the box. I hate to junk up my system tables, though. So what I usually do in situations like this is just add a link back to those. So instead of a mini to mini, we'll just do like a mini to 1. And we'll call this our user. For the related collection it's gonna be directus underscore users. And I'm gonna go into field advanced creation mode, we'll call this host profile. And one other thing I'm gonna do is make this value unique. Great. Okay. So now we have our host profile. What does this look like? Santa is our host. Do we have any other details? Let's pull somebody else up. I'll click the same one. See what kind of data we're storing on the host profile. Alright. Probably an image on the host profile. Great. So we'll just add an image, or we could call this Avatar. And how are we doing on time? Looks like we've got roughly like 15 minutes left. We're doing a lot of data modeling on this. We've got our user avatar. We've got a description. Great. Alright. Let's go in and we've got the details for that. Let's add our host to the listing as well. So a listing belongs to a single host. I see we've got co hosts here, but, not super concerned with that. That's gonna be our many to one collection, or relationship. So a host owns a single property or a host is responsible for a single property, but a host could have many properties. So we'll choose our host profile. Great. Save it. Okay. Alright, so let's go in and look at this giant form that we're gonna add all of our data. Right, now the really cool part here is I've modeled all this data out, and I'll show you in a moment. I'm just gonna copy and paste basically everything from this. If I was smart I probably would have built like a scraper or something to input all this data. Cool. There we go. We've got the description when it comes time to the amenities. Let's just go in and create a new amenity. We've got mountains, view. Yeah, that kinda looks like a mountain view. Mountain view. We could give it a key. That could be helpful if we're actually filtering this on the front end. Dedicated workspace. Alright. So I can go through and do a desk icon. Not quite as fancy as what they have here. We'll just use Workspace as the key. Cool. Great. So we added some amenities. This is what? This is a entire cabin? Yeah. This looks like a house. This is an entire home. We have up to 4 guests. You can see we've got that nice little note that we had here, so this is just providing that extra little for our user experience. We've got 1 bedroom, 2 beds, 1 bath. Are pets allowed? Do we see pets on this one? Let's say no. Right? Check-in time, where do we find that? We've got our check-in time is 4 pm. 4 PM. Check out time, 10 AM. K. Check out type. Self check-in with a lockbox. Do we have a quiet time? I don't see quiet time. I'm gonna set quiet time. Right? From midnight to 5 AM? I don't remember. Great. Additional rules, we can see outside guests, no allowed parties, lots of additional rules at this particular place. We've got the address, I'm not even going to bother filling that out. We'll add some property info. What do we have there? Must Climb Stairs. That's a great one, right? Must climb stairs. You have to be able to climb some stairs to access the property. Stairs. We even have a nice stairs icon. Great. We don't have any reviews. We're gonna tag this as a cabin in the woods. And then we have our host. Right? So we'll just create a new host profile. Who is our host? Sierra. I I'm not gonna do that. Let's just use, like, user avatar, sample user avatar. Yeah. This guy looks great. Kinda like Tim Cook or something. Copy image address, upload, we'll call this guy test host, and save. Alright. So now we've got a listing. The only thing we're actually missing here is some images. Right? So I can go back into our listing, I can hit create, and here we can add files to this. And files could be images, we're just gonna call it images, that's great. We can even choose what folder we want to upload these things to. So we'll save that, drag it back to the top, add some images for this, and where'd you go? I'm just gonna pick one of these. One of the things that I really like is, it's super handy to be able to upload images from a URL here without having to download them. Not sure about you, but I usually get like a 1,000,000 1,000,000 files on my desktop that, you know, just clutter up things after a while. Okay. Alright. So now we've created this listing, right? And, if I pull up our application, we got this really nice Nuxt application. I'm gonna just move Airbnb out of my way as well. But if I pull up this application Oh, I closed Directus. Let me bring that back. I still haven't mastered Arc. I'm not sure if you guys are Arc users or not, but, it can be a challenge. Alright. So we've got Directus, we've got our local host. I'm gonna pull up our code base. This is just a senior Nuxt application. I've already got an SDK configured for this. And if I look, I've got an extra leftover page here, but we've got this index page, we've got 100 apps, 100 hours. If I wanted to just use like the async data call from NUXT. So if I look at the NUXT documentation, they have a composable in here, use async Data for Data Fetching. And this takes care of things like caching, refreshing, it gives you like pending state, things like that that you can use. So I could potentially just copy this here. We've got Use Async Data. Let's call this Listings, so we give it a key. This could be like the actual route, like the full path of the route. But here, I'm going to go in and we are going to do something like this, where we say return use directus. I've got a composable here and I'm going to read items from the listings collection. The next thing I would add here could be, you see Copilot is already offering some suggestions of hey, only show me published listings or like the specific fields that I want. Let's not even bother with that at the moment. I may just leave that on there, though. Great. Do we have everything we need? Refresh interval. Where am I missing? I don't need refresh interval. Don't really want refresh interval. What does our parser say? Do I have too many of these? Let's just keep it simple. Oh, yeah, missing the last one. But there's that's what I was missing. Okay. So now I'm just gonna log this out. So if I do data, maybe we wrap it in a pre tag just to see what that looks like. Are we getting anything back from Directus? No. Why not? Subscripts. Parse error. Use async data listings. We're going to return use directus read items. That should be what I've got set up here. Use directus. Use directus rest. No. It should just be oh. Do we need to return a wait? Nope. What is the problemo? Let's actually just ditch async data for a moment. Const data equals use await. Use directus, read items listings. Alright. And this should do the fetching here. And now I can see the problem. Nuxt has SSR configured out of the gate, so I wasn't seeing those errors here on the client side. But basically this is showing 403 forbidden. And the reason why is because I haven't set up my permissions inside Directus. So if I were to just log that back, I could go into Directus and under our rule based access control, I'll just go in and for now, and because we're running out of time here anyway, I'm just gonna set this to public. All this data that we set up for the individual listings, all of our tags. One of the things that I could do here, right, this is public data, so I don't want to maybe I want to show the city and like the state or the country, but I can go in and I can set custom permission settings. So where I have the, you know, I could go in and say okay, only items that are published will be shown and are available to the public, so without login. But things like street address and postal code, you know, if I was storing data like phone numbers or email addresses, right, I wouldn't want to make that data available to the public. You know, the rest of this data, hey, they need to know and we probably still want to surface that if they are, just browsing the site and they're not logged in. But for the actual sensitive data, I don't want to I don't want to give that up. Alright. We've saved our Let's just take a look and see what we get back here. Okay. So I can see the listings. Right? We're not getting any data. And again, I thought I was using async data wrong here. I need to brush up on its use, but the status of this post is not published. So now if I go in and publish it, because we set up that rule based access control, Now we can see we got some data. It's rendering kind of funky because I'm flexing it. Okay. Alright. So now we can see we're actually fetching all the data from our API. So which is really nice. Directus gives us these REST APIs. As soon as we set up our data model, we can see all this information inside here. All right. So if I look at the clock, we've got, just a few moments left. Let's go in and just try to flesh something out as far as a detail page. If I look, I've got some images here. How do I actually get access to this relational data? One of the nice things inside Directus is the ability to pass a fields array that will actually act as a GraphQL API. So you get all the benefits of GraphQL, and then I can specify all the fields that I want to return, and I can even grab relational data, as deep as I want to go, which you have to be very careful about recursive relationships. But within that, I can go and grab all of that. So if I wanted to grab the root level fields, I could. I could also do something like this where I say images.asterisk, that will give me the Directus Files ID that I would need to render these types on, on the actual on the site. Yeah. Simple enough. Alright. Let's flush this out as quickly as possible. Alright. So I'm gonna clean that up. What do we have here? Underlying this, I've been using the Nuxt UI library, but I'm always a huge fan of Tailwind UI. If you don't have it, sign up for it. It's a great tool. All right. This is probably, could be similar to a product page, maybe. Alright. Let's take a look. Product overviews. Got a couple of these. I really like this image grid component. Image gallery. Let's just lift this wholesale. Oh, the Linter is doing a number on this thing, right? So here we go back. We're probably getting an error somewhere. The View Dev Tools and the Nuxt Dev Tools, I can't recommend enough. They're very great. But if I go in and I look for my index page, I can see our data that we've got here. And maybe here, I actually want to, is there where's the key? I think I actually do something like this where I transform and I do data, and I do data dot 0. Is that valid? No. Yeah. Maybe that's right. No overload. Not just this call Because we've got the return. Oh, no. I think it needs to be one more out here. Okay. Now if I look, data transform. Product dot images. Okay. So this is gonna be data dot images. So if we just clean these up. Data data dot images. Oh, missed one. We'll just clean that up. Data dot images dot source. Yeah. So we're not getting source here. Are we getting any actual data at all? Right? It's not showing me anything in the view dev tools. This could be a case of where I spent way too much time data modeling, without actually building anything. But at least, you will have a good idea of the back end structure. 0 of undefined. Let's just, comment this out. Get our data back. Data. Pre. Alright. So we have data dot images. Now I gotta do data dot 1. Let's just do this. Our listing equals data, data dot value, and we'll log our listing. Okay. And now I should be able to do something like this where, I've got Nuxt Image connected to this as well, which is a great tool. So we use Nuxt Image and in the actual config for Nuxt you can set up the provider for Directus because we use the same underlying library. Where are you? And then I can set the base URL for my assets. So then I don't even have to actually worry about passing the URL. So Nuxt data dot images. Is it gonna be data? It's listing dot images, and then where it says source, we're actually gonna grab this directus file's ID. Underscore files underscore ID. Do we actually get images here? If I remove the alt text, does this actually work? Am I are are we kidding, man? Why do I not have this? Right? If I close this out, why is it not showing? Images, directus files underscore ID. Yeah. Alright, well regardless we hit the timer. I didn't get very far on the front end on this one. I chalk it up to spending too much time on the back end, but oh, what a back end it is. It's very beautiful. We've got a detailed data model that, we've got our REST API for, if you prefer GraphQL, you could query this on the front end to your heart's desire. Again, maybe I was rightfully a little scared of this Airbnb just because of everything that is involved in all the details on this. Anyway, I had a lot of fun with this one. I hope you enjoyed it. Stay tuned for the next episode. Got a great one coming. I'll 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,166,122,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","9271a4fc-addf-4400-9591-f2f2ec07bd79","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",1773850452397]