[{"data":1,"prerenderedAt":441},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-netflix":121,"100-apps-100-hours-netflix-next":173,"sales-reps":189},{"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":171},"a311b57d-34e7-4073-9cf3-6a8c6c0f8b85","netflix","896165856","It's Bryant vs the streaming giant – Netflix – in this exciting episode. He has just one hour to build an app that replicates the core functionalities of the video streaming service.","012d9dd3-99b5-40ee-8811-9928bdc0a5bc",69,[129],{"name":130,"url":131},"Bryant Gillespie","https://directus.io/team/bryant-gillespie",8,"2024-01-29","Mission: Netflix Clone","\u003Cp>Speaker 0: Alright. Welcome back to the next episode of 100 Apps, 100 Hours. I'm your host, Brian Gillespie, developer advocate at Directus, and I'm super excited for today. We're gonna answer the question that has been keeping you up at night, can you build Netflix in an hour? You've probably not been keeping you up at night.\u003C/p>\u003Cp>But the truth? You can't handle the truth. I can't resist throwing in a good movie quote here for this one. So we are going to try to rebuild Netflix in 1 hour or less or publicly fail trying. Those are the rules of 100 apps, 100 hours.\u003C/p>\u003Cp>And then the second rule of Fight Club is use whatever you have at your disposal. So how are we going to achieve this? We are going to do a little planning, then we're going to do a little building. But just to get eyes on what we're going to build, here's Netflix. You know it.\u003C/p>\u003Cp>You've seen it. We are going to try to rebuild as much of this as possible in 1 hour. So that means the back end, that's where we'll start. Then we'll work on the front end and try to get as close as possible to the original Netflix or, my own spin on it as possible. So what do I have prepped ahead of time?\u003C/p>\u003Cp>I have a Nuxt application ready to go. I've also got an instance of Directus. Directus is what we're gonna be using for the back end. I've got it spun up. It is totally blank now and before we get started, I'll show you just so you know I'm not cheating.\u003C/p>\u003Cp>So we'll go to admin, example, we'll key in a quick little password. If I can actually remember what the password is. There we go. Okay. It is a totally blank instance, and if we fire up the database, again just to prove it, we've just got our directus collections that it adds for us automatically, so just the metadata.\u003C/p>\u003Cp>But as far as our actual data, there is not. So with that out of the way, I'm sure you guys trust me anyway, but let's get started. Right? We got 60 minutes on the clock. Boom.\u003C/p>\u003Cp>Away we go. Alright. So before we even get started, you know, maybe this is not the Netflix clone. This is, what are we going to call this, right? Give it a name.\u003C/p>\u003Cp>How about Dev Flix? Dev Flix, yeah, this is essentially what this could be. So this is Dev Flix, let's strike through this. This is what we're going to be building. And anytime I build a project, I I like to do some concepting first, maybe some diagramming, just so my brain is warmed up and I understand what's going on.\u003C/p>\u003Cp>So we'll just draw some boxes here. Let's walk through the functionality or do we even need to cover functionality, right? We all know what Netflix does, but we've got a, browse a catalog of titles. Those could be a movie or a show. What else?\u003C/p>\u003Cp>View that content. View stream videos. Right? Stream Videos once title is selected. Pick From different categories.\u003C/p>\u003Cp>Yeah, I don't know. This is Netflix, right? We all understand the functionality involved. Alright. So let's start working on the data model.\u003C/p>\u003Cp>Right? One of the ways that I will usually try to deconstruct application is just open up the JavaScript console and or the developer tools, and not necessarily the console. And then take a look at, like, the network requests that are coming in to see if I can understand what's going on behind the scenes. You know, I don't know that Netflix does Netflix have an API? Does it close its okay.\u003C/p>\u003Cp>So they closed their public API. But if we look, we could see there's a ton of calls going out to a player, but I see here's like some GraphQL stuff, some videos, artwork. I'm not really sure if this is gonna get me anywhere behind the scenes. They spend a lot of time on security and things like that and, you know, performance. So maybe they're doing some server side rendering and and things like that here to actually display this content.\u003C/p>\u003Cp>No worries. Right? But let's walk through and and just kind of think through things. Right? We've got what do we have inside Netflix?\u003C/p>\u003Cp>We have a, like, a title, like a content title, or, we could call it content, but that doesn't seem right. It could be a movie or a series. So let's just call it titles. Right? These are movies, series.\u003C/p>\u003Cp>There's a name for that, description, probably like a rating. You know, there's a lot of things. So we just wanna capture, like, the core stuff here. Alright. So we got titles.\u003C/p>\u003Cp>What else do we have as we go through? If we look at My Little Pony here on the screen, we've got our episodes and seasons. So if it's a show, we've got a season. Season has a number, probably, what, a release date for the season. And then it's gonna have a title ID and probably episodes.\u003C/p>\u003Cp>Right? Episodes. Now behind the scenes, I don't know exactly how Netflix Netflix has set up, like, their video and, you know, like, the the encoding and and streaming and all of that. I'm assuming attached to each title or each season, each episode. Alright.\u003C/p>\u003Cp>What do we have? Oh, sometimes you just accidentally push command q and totally destroy all of your progress. Alright. So how are we gonna structure this? Right?\u003C/p>\u003Cp>This is probably the most important thing to get right. Each episode is going to have a episode number, name, description. It's gonna be back to, what, a season, season ID. And then there's almost like another relationship here, which is just the actual content. So this is just as, like, the actual file.\u003C/p>\u003Cp>We'll probably just use, like, a YouTube URL in this case. Okay. So these are our kind of main collections. You probably also got things like ratings, users. Directus gives us that out of the box, which is really nice.\u003C/p>\u003Cp>So we've got Directus users. And, you know, we could have something like viewing history as well. User watch history. You know? We're getting really deep.\u003C/p>\u003Cp>Like, the main functionality is just up here where we have these items. Right? These are the core pieces of this application. Alright. Shoot.\u003C/p>\u003Cp>I forgot to start the clock. How long have we been doing this? Let's just give, like, 55 seconds or 55 minutes. Or I did start the clock, but we deleted Figma. Right?\u003C/p>\u003Cp>So let's handicap it a bit more. Maybe 54. Okay. Alright. So, we'll go back and look at that during editing, but, yeah.\u003C/p>\u003Cp>Sorry. Mistakes happen, especially when you're against the clock. Alright. So now that we've kind of got a rough idea of how we're gonna structure this, I like to work back end first because when I'm building my front end, I love having access to the actual data instead of lorem ipsum, dipsum, and then I have to wire everything up later. So with this stack, Directus and Nuxt, I can quickly scaffold my back end and get instant rest and GraphQL APIs, and then plumb that to my front end as I'm building the front end.\u003C/p>\u003Cp>So, it's a a really great workflow for me. I hope it works well for you. Give it a shot. Alright. So let's go in and create our first collection.\u003C/p>\u003Cp>We're gonna call this titles. And I'm sure somebody will tell me why this is wrong, why it is not. So Directus gives us a couple of system fields that we can add. These are just little helpers, like, date created is automatically populated with a time stamp. User created is automatically populated with a logged in user that created that.\u003C/p>\u003Cp>Do we have a sort for those? And I can already tell I forgot one big one, right, which is gonna be genres or categories. Like, we've gotta have some kind of taxonomy for those. Alright. So we've got our title.\u003C/p>\u003Cp>What is the type of this? Right? So it's either gonna be a movie or a series. Let's use our radio button interface for that. So this is gonna be the type.\u003C/p>\u003Cp>The first one will be movie. So it's either a movie. Let's say we default to movies. This is gonna be series. And and Directus also has built in translation strings, which is another nice thing that I could go in and create these translations where here's the key.\u003C/p>\u003Cp>Let's call it what? Series. Right? Series. And in English, this is gonna be let's just capitalize this field anytime we render it.\u003C/p>\u003Cp>Right? Cool. Alright. So then I could do something like this where I have dollar sign t and do series, and that will automatically translate that for me based on the strings that I've got set up, Which is really nice if you've got users across different languages inside your Directus instance inside the back end. Alright.\u003C/p>\u003Cp>So we got the type is either a movie or a series. Let's add an input for the name of this title. And I can even go in and add like a little helper. What's the name, in this notes section? What's the name of the content of the movie or series?\u003C/p>\u003Cp>Right? Or series. Great. What else do we have? We got a description.\u003C/p>\u003Cp>And let's just go for, like, a rating. So we got a drop down. Let's call it rating. Or, you know, we could probably have, like, a ratings collection here. I was probably thinking this is like reviews, but, let's just take a look and see.\u003C/p>\u003Cp>Right? This says this is TBY. Do we wanna be able to query by that? Right? So in that case, let's do a many to 1, and we'll call it a rating.\u003C/p>\u003Cp>And we're gonna have Directus create a new collection for us in the background. So everything that's going on here, I feel the need to explain this, Directus is actually mirroring this inside my database. So as I create these new fields, as they're called inside the Directus application, it is actually mirroring those to my SQL database. So we'll call this ratings. And if we go to advanced mode just to take a look at this, maybe we zoom back out a little bit.\u003C/p>\u003Cp>But I could see that Directus is telling me that, hey. We will create this inside your data model. Great. So, it's gonna create that for me. And then we've got a mini to 1 relationship.\u003C/p>\u003Cp>Great. Okay. So we got the rating. And now if I look, we've got our ratings collection created as well. Alright.\u003C/p>\u003Cp>So we'll go in, let's add a rating, the name for the rating. I'm not sure if that's what these are actually called or not. And let's just create a couple of these. Right? Like pg13, TV y.\u003C/p>\u003Cp>Is that what I saw? TV y. Yep. TV MA, rated r. Yep.\u003C/p>\u003Cp>Alright. So we get the the picture. We could call it PG, and then we have G. Right? Some of my favorite movies are still g rated movies, to this day.\u003C/p>\u003Cp>Of course, I'm a father of 3 as well. Alright. So we go back to our data model. Let's build out the rest of this. We've got our titles.\u003C/p>\u003Cp>So if we are creating a title now, like My Little Pony, now we can see what the status is. Is this a movie? Is it a series? What's the name, description? And we could pick a rating that we can then update through the system.\u003C/p>\u003Cp>And let's just adjust the display template for this because I wanna show the name anytime we're referencing that rating. Alright. So I'll zoom back in just a bit, and we'll build out our next piece of the puzzle here. Let's work on seasons. Alright.\u003C/p>\u003Cp>So we'll automatically generate a UUID. Maybe we wanna store who is the user that was updated this just so we can play the blame game later. Cool. Alright. And then we've got a number.\u003C/p>\u003Cp>So let's do an integer. We'll have the season number. Okay. And inside Directus, I can also make that the sort field. Where are you, sort field?\u003C/p>\u003Cp>Alright. So we'll choose the sort field. The season number is going to be the sort field. So whenever we reference this, it will sort by that number automatically. What else do we have?\u003C/p>\u003Cp>Do we have a description on the sort number? I I don't know. Release date, you know, this theoretically could be on the actual episodes as well. But Netflix does like to drop a season at a time, so maybe we store it here as well. Let's just use a time stamp so that it saves the the time zone value as well.\u003C/p>\u003Cp>Alright. What else? We have a title ID. Right? So we have to create that relationship back to the title so we can fetch that data.\u003C/p>\u003Cp>So this is gonna be a mini to 1. Right? A season can only belong, like, to a a single content title. So if if the show is My Little Pony, there's 5 seasons, all those seasons belong to 1 My Little Pony title. So we could call that title ID or title, I prefer less verbose, but it doesn't really matter here.\u003C/p>\u003Cp>Directus is going to serve up that however we create it. Alright. So we've got that. Let's add the one to many relationship. So we're creating a many to 1, but on the reverse side, a single title could have many seasons.\u003C/p>\u003Cp>So let's go ahead and create that relationship as well. And for the season we'll use the oh, for our many to 1 we'll use the name of that and then we'll do related values. Okay. So we got the title, we've got the number. You know, we could have a description for this season if we wanted to.\u003C/p>\u003Cp>You know, there's probably even, like, some collateral or trailers or or something attached to this. We won't worry about that right now. Let's save this, and then we'll go in and create episodes. So in our diagram over here, we probably should have had episodes to begin with. Alright.\u003C/p>\u003Cp>So same thing. I'll I'll just add my system field. So if anybody updates this information, we've got it. Is this episode published or not? Yes or no.\u003C/p>\u003Cp>And then we'll give this a name. What's the name of the episode? We probably also have a number for this episode as well. Episode number. And, you know, something like this, you may even call, like, episode number just to make sure everything is very clear, but I'm all for making things difficult.\u003C/p>\u003Cp>So we will add a description. And now we'll link this back to the season. Right? So we're going to use that many to one relationship again, and we're going to link this back to a season. The related collection is seasons.\u003C/p>\u003Cp>And I'm going to open up advanced field mode here in Directus, and I guess I could actually use my little mouse pose tool for you guys so we can get an idea of what's happening here. So I'm gonna add the reverse relationship back to seasons. So one season could have many episodes, 1 episode has or many episodes have a single season. Think I explained that right. Sounds great.\u003C/p>\u003Cp>Alright. So now we've got titles, we've got episodes, we've got seasons. If I look, we've got titles. So let's go in and create a new title. Right?\u003C/p>\u003Cp>So we'll just drag and drop Netflix over here. And let's just work on this. Right? My Little Pony, Make Your Mark. Alright.\u003C/p>\u003Cp>So this is My Little Pony, Make Your Mark. Alright. Maybe there's a hyphen there, a dot, I'm not sure. That's the season description. Right?\u003C/p>\u003Cp>Where do we see all episodes? Alright. So here's the there's kind of the the generic description that apparently Netflix does not let me copy. Alright. So welcome back to Equestria, where pony magic is everywhere.\u003C/p>\u003Cp>Who doesn't love pony magic? Alright. There's our description. This is TVY. Alright.\u003C/p>\u003Cp>And I can already see we've left out a couple things. Right? We've left out some thumbnail images, probably on each episode, each, title as well. But we'll go in and create a new season. They're calling this chapter 1.\u003C/p>\u003Cp>So we probably have a name for the season as well that we can add. Let's just say the release date is today, and we'll start adding some episodes. Right? So we got Make Your Mark. That's episode number 1.\u003C/p>\u003Cp>Something's wrong in Equestria. And now we're actually watching Netflix. How do I go back? Okay. Alright.\u003C/p>\u003Cp>Is there a way to actually I I guess they disable that. I'm just gonna use Raycast here to generate descriptions for this. Alright. So we'll say this is published. Alright.\u003C/p>\u003Cp>So at the very least, now we have some stuff that we can work with and fetch via the API that we can render on the front end. I'm gonna go back really quickly and just add a thumbnail for this title. And we could even do that for what, the episodes as well. We probably got, like, a thumbnail. There's probably also, like, a teaser.\u003C/p>\u003Cp>And while we're at it, let's just go ahead and try to finish out what we were building on the back end. Alright. So then we've got the actual content. So creating this separate collection allows me to standardize this across episode or, like, TV and movies. So, let's go in and create the content.\u003C/p>\u003Cp>You know, this could be video, I guess. Yeah. I'm not sure. Let's just call it content. And I'll probably figure out a better way to do this at some other point.\u003C/p>\u003Cp>Alright. So we got the content. We've got a just use a YouTube URL for this. We're gonna cheat a lot. But this could be like an actual file that we uploaded to our back end here that we serve.\u003C/p>\u003Cp>Again, I don't want to get into, like, the streaming stuff because I like efficiently serving video at different bandwidth, at different resolution. That's a whole ball of wax. I'm sure there's a great API for it. And Directus will manage some of those assets for you in a nice way, but it doesn't do video encoding and things like that that you need to deliver those streams effectively. Cool.\u003C/p>\u003Cp>So we've got, what, a YouTube URL. There's the content. This is gonna have a title associated with it. Titles. K.\u003C/p>\u003Cp>And then there's probably what? Episode as well? Episode. Titles. Okay.\u003C/p>\u003Cp>Alright. So we got a title. We got an episode. We probably should have created those reverse relationships, but we could go in and do that now. Alright.\u003C/p>\u003Cp>So we'll call this the content. That'll be our content collection. Episodes. And now we'll just create the reverse. Uh-oh.\u003C/p>\u003Cp>What am I doing wrong? I'm doing a mini to 1 back to that when, actually, let's just go in and I'm gonna delete these for now just to make sure we set these up correctly. Alright. So I'm gonna go let's go to the titles. We'll do a many to 1.\u003C/p>\u003Cp>We'll call this content. We'll go to the content collection. And here, I am going to go into the relationship. We'll add this to the title. Okay.\u003C/p>\u003Cp>So we'll add that reverse one to many relationship. We'll call it the title. I think it is going to be an array. I guess you could have multiple pieces of content attached to any one item, but no worries. Content.\u003C/p>\u003Cp>And then we're gonna have episode. Okay. Okay. Cool. Cool.\u003C/p>\u003Cp>Cool. Alright. What's next? Right? We probably need some genres.\u003C/p>\u003Cp>Let's go in genres. And if there could be multiple genres per piece of content, I assume, like, could be drama and horror or drama and faux drama. If you live in my household, I've got plenty of drama. So we'll give this a name. And then let's go in, and we would create a junction table between titles and genres.\u003C/p>\u003Cp>Right? But, again, one of the nice things here is Directus is gonna do a lot of this for us. So we could call this genres. We do genres as the related collection. And if we look, we go to the advanced mode inside Directus, we can see that there's gonna be a junction collection or a junction table in our database created.\u003C/p>\u003Cp>Do we want to be able to query by genres? Maybe we don't need that. Do we add a sort field? Probably not. Okay.\u003C/p>\u003Cp>So now we've got genres. We've got thumbnail. We got a lot of different items set up in this database. Right? And we're closing in on 30 minutes.\u003C/p>\u003Cp>We probably need to start building some UI soon. But I do wanna just open up the database itself and and take a look at what's going on now. So you'll see these direct us tables. But if you look, you can also see our our actual tables that we created, like episodes and genres. And if I look at the data, here's the episode that we created, make your mark for My Little Pony.\u003C/p>\u003Cp>So this is great. Like, anything that happens inside the app here, Directus is mirroring that to your database. Likewise, if I go in and, there's very robust access control here. I'm just gonna enable this as a public permission for all of our content, which, would probably not be a great thing security wise, but I just want to show you this. We'll go into local host 8055, which is my back end.\u003C/p>\u003Cp>And if I go to items and I go to titles, boom, I'm gonna get My Little Pony. Right? Now, this is not super helpful because we're just getting you know, like, let's say I I just wanted to make a single call and get all my content. I love Directus for that because it gives me an interface via REST that feels like GraphQL. So I get the benefits of GraphQL and then I can go in and do something like this, like, hey, I just want the ID, I want the type, I want the name, I want the description, Boom.\u003C/p>\u003Cp>And it will give me exactly what I asked for. Now, the other thing that I can also do is go in and if I want something like seasons, and I I could use a wildcard to grab all the root level fields of the the next one, right, but I can get all of my related content in a single API call. That's super powerful, super helpful. Obviously, like, the deeper you go, the slower the queries on the back end are gonna be, but it is still very speedy. And, obviously, I don't have a ton of data here.\u003C/p>\u003Cp>So we got seasons dot star, and then we could do seasons dot episodes dot star. And that should give us the episodes. And then once we add content, we could go even deeper and say something like seasons dot episodes dot, content dot star. Now there is no content at this point, but if there were, it would come back. Great.\u003C/p>\u003Cp>So that is just one way that one of the major things that I really love about directives. Alright. So we've got some stuff going on. Let's go in and populate a few more titles. Right?\u003C/p>\u003Cp>We've got a thumbnail. I don't think they're gonna let me lift this. So let's just do the old screenshot trick. Upload that image. Click to browse.\u003C/p>\u003Cp>Should actually just be able to drag and drop there. Okay. Let's create a new content for this. We'll call it YouTube. Pull you up, My Little Pony.\u003C/p>\u003Cp>And again, I I don't support any of this. Make sure anything that you are doing is okay legally. We'll just copy this. K. Great.\u003C/p>\u003Cp>We've got a movie. Actually, this is a series. Right? But one thing I noticed here is if I'm switching between movies and series, we should probably actually not show our seasons. Right?\u003C/p>\u003Cp>So how do we actually adjust that? Well, if we go into our titles, Directus has the ability to do, relational or, I'm sorry, conditional fields. So I could go in and for seasons, we could do something like this where we hide this by default, and then we come into our conditions tab. And if type equals series, so then we build a rule. This is just a description for this.\u003C/p>\u003Cp>We'll do type type equals Series, then we are going to make this not hidden. Right? And we'll show a link to the item. Did I get that right? Yep.\u003C/p>\u003Cp>Okay. Alright. So let's test this out just to see what's up. Let's go in and add another one. Right?\u003C/p>\u003Cp>And now where I have movie selected, nothing. But if I select series, now we're seeing that field. It doesn't change the underlying database structure or anything. It's just hiding that when I'm creating the content. But I could even add validation around it if I wanted to.\u003C/p>\u003Cp>So if we go in and we add a movie, let's see if we've got a movie. I could switch up here to the top to movies, and we open this up. We've got a trash truck Christmas. Christmas. Alright.\u003C/p>\u003Cp>This is TV Y, made for TV, made for TV goodness. Alright. We will create some new content for this. Where's our handy YouTube? Trash truck Christmas, full episode Netflix Junior.\u003C/p>\u003Cp>This looks like an official channel. Yeah. Okay. We'll see. So we got that.\u003C/p>\u003Cp>We've got a thumbnail image. Again, I'm not even gonna try to lift this from oh, actually, I I guess I could. Copy image address. Is that available? On the web?\u003C/p>\u003Cp>Okay. Cool. So we'll just lift these images directly. I can import that file. Great.\u003C/p>\u003Cp>There that is. And I could, you know, maybe potentially find their GraphQL, stream for this that allows me to pull all this in, in a a more timely fashion. But, okay. Genres. Right?\u003C/p>\u003Cp>Let's create a new genre. What are we calling this? This is kids TV. There's a new genre. Okay.\u003C/p>\u003Cp>We'll create a new kids and family movies. And this will be TV cartoons. Great. Okay. So we got a movie.\u003C/p>\u003Cp>Let's switch this back to a series. Right? Lots of series because we've got seasons attached to it. Got some content for it. Is it is that gonna be right?\u003C/p>\u003Cp>Actually, it's not, is it? We're going to go back to My Little Pony. We'll go into the seasons. We've got episodes, and we should have content for that episode. Cool.\u003C/p>\u003Cp>Alright. So let's just see what this all looks like. Great. I could even go in and adjust this to, like, something akin to Netflix if I wanted to where I could see, like, the thumbnails of all these different titles. So give it a name.\u003C/p>\u003Cp>The subtitles could be what? Description. What else do we have? Or it could also be can we get genres in here? Genre genres ID.\u003C/p>\u003Cp>Let me get the name of the genres. Cool. Alright. So we got a couple titles. We are what?\u003C/p>\u003Cp>We're pretty I'm pretty happy with where the back end is at at this point. You know, we could really go through this in a lot more detail and create something like actors, you know, the cast and crew, director, things like that. And those would probably all be different database tables. But for the sake of having something to display at the end of this next 28 minutes, let's just get to work on the front end. Alright?\u003C/p>\u003Cp>Cool. So we will let's put you back over here, Netflix. Close you. We'll close our database. Let's pull up our Nuxt application, and I'm just gonna swap these around.\u003C/p>\u003Cp>No real rhyme or reason. I just like my text editor being on the right side. Alright. So now let's pull up our Nuxt application. Oh, this thing is a beauty, right?\u003C/p>\u003Cp>So, I've just got a preconfigured, like, a Directus Nuxt module set up that basically just wraps the Directus SDK with a little bit of auth. You know, we may not even dive into that. But, and then I've got a use direct as composable that allows me to make requests easier. There is a a little bit of setup work that I've done here, but it is really just, one of our boiler plates HSC OS that I have taken and stripped back for the purposes here. Alright.\u003C/p>\u003Cp>So we go into our index page. Alright. So we let's look at, the homepage here of Netflix. And we've got kind of this setup of a header. We've got a grid of titles sorted by genre.\u003C/p>\u003Cp>Alright. So let's dive into this. The first thing that I'm going to do, just because we are short on time, is take a look at, Tailwind UI. Advanced. What's going on here?\u003C/p>\u003Cp>Okay. Let's just look at Nux UI as our our library here. Something wrong with my laptop at this point. Alright. So inside this boilerplate that I've got set up, I've got a little, I've got I've got this Nuxt UI library, which is kind of built on Tailwind as well, that gives us some nice things like modals, some of the trickier components to actually build.\u003C/p>\u003Cp>Right? So we go in. Let's try Tailwind UI again, now that I've refreshed a little bit. And, alright, so what are we looking for? Let's grab a, like, a header from Tailwind UI.\u003C/p>\u003Cp>Alright. This looks pretty good here. So we've got the view version. I'm just going to copy this down. Alright.\u003C/p>\u003Cp>And let's go in and create a header, error component header dot view. Alright. Are we gonna have a logo for this, right? We've got our logo. Can we pull that up?\u003C/p>\u003Cp>Will this let me copy as PNG? Can I export this as JPEG? Nope. Let's just do a new Figma file actually instead of FigJam. Can we paste that?\u003C/p>\u003Cp>If we copy, Can we paste? Devflix. Yeah. Alright. Let's give it some, like, type of little icon treatment here.\u003C/p>\u003Cp>So we'll add a real original logo here. Looks like a play button. We'll change the fill to red. Right? Okay.\u003C/p>\u003Cp>And maybe we, what, let's wrap this in. Let's give it a little oh, yeah. I'm I'm gonna be giving design courses after this as well. Alright. So we'll take this.\u003C/p>\u003Cp>We'll export to SVG. Let's just do logo. Great. Alright. So we've got this header component.\u003C/p>\u003Cp>If we go back to Netflix, what do we got at the top? We've got, home. That'll be our index page. This is for our navigation. Right?\u003C/p>\u003Cp>So we've got TV shows. Alright. We got movies. Great. Okay.\u003C/p>\u003Cp>Cool. I don't have Headless. I don't have heroicons in here, so let's just comment those out for now. Bars 3. I'm sure that'll throw a fit when we actually take a look at this.\u003C/p>\u003Cp>But cool. Alright. Now I'm gonna create a new file called logo dot view. And we're just gonna copy this as, what, SVG. Paste here.\u003C/p>\u003Cp>Nope. I need to wrap that in a template, though. Alright. There's our SVG. Now inside our header component, I'll go back up here to where we have Tailwind UI's logo.\u003C/p>\u003Cp>And I'm just going to call that logo component that we just had. Don't need a source. Don't need an alt tag. Well, we probably do, but not going to have one. 100 apps, 100 hours.\u003C/p>\u003Cp>Okay. So we have a header. We're not seeing it here. What I can do is adjust the default layout. And let's do something like this where we have BG gray 900 and probably text white since we're kinda going on the Netflix theme.\u003C/p>\u003Cp>So this is the layout inside Nux, which is a feature that I really like about Nux, is the ability to set up a default layout. And boom, we've got our DevFlix header looking nice. If we go back to that header, probably wanna swap these a tags or a Nuxt link and change this to 2. And we could change this to TV shows and movies. Right?\u003C/p>\u003Cp>We just have to create a route for those. And I'm not sure exactly how far we'll get because we have, what, 21 minutes remaining. But we'll we'll see how far we can get. Alright. So if I go into the header, we need to update the details for it as well, like bg white.\u003C/p>\u003Cp>What do we what does Netflix look like on the header? It's just kinda transparent until you scroll down. That's a nice effect. I don't want to set up anything there. So let's just cheat.\u003C/p>\u003Cp>We'll call it bggray900 again. And then that's gonna mess up a lot of our text, I believe. Right now, we can no longer see all of that. So we go to our text. Let's look at anything with text gray.\u003C/p>\u003Cp>Can we replace that with we could probably do smaller values, like text 200. Again, I love Tailwind just because it enables me to not have to write custom CSS for everything. Like, it it does take some time to learn the conventions, but but being able to not write CSS, I like custom CSS, just helps me speed along. Now I know it's not for everyone, so don't let me tell you that it is, But I enjoy it. Alright.\u003C/p>\u003Cp>Can we get our stuff? Okay. Alright. That's probably because we're missing an icon for our navigation over here. So what is this?\u003C/p>\u003Cp>This is hidden. Flex LG. Let's hide that at, like, just a smaller breakpoint. Yeah. Okay.\u003C/p>\u003Cp>Cool. Alright. So we got home. We got TV shows. We got movies.\u003C/p>\u003Cp>We can log in. Great. Now let's actually work on our page. Right? If we go in, let's actually render some stuff out.\u003C/p>\u003Cp>Alright. So if I look at Netflix, we've got a, like, a header component or, like, a hero image. So we go for our hero sections. Hero. Which one of these kinda looks like Netflix?\u003C/p>\u003Cp>Kind of this one? Kind of. I mean, this one has a background image. So we could take that. Okay.\u003C/p>\u003Cp>So we're not gonna copy this header piece, but let's just go in. I'm gonna copy this part. We will create a new one called hero dot view. I've got a just a couple of snippets already set up for me inside Versus Code to make creating Vue components a little easier. And k.\u003C/p>\u003Cp>We're good there. Watch chapter 6 now. Is this just kind of a rotating thing, probably based on, like, the last thing that you watched? Instead of doing that, let's do something else. Data.\u003C/p>\u003Cp>Alright. Alright. So this is gonna be justified center. We don't wanna do that. We wanna do does it justify start, I believe?\u003C/p>\u003Cp>K. Oh, we'll add this to our index page. So let's take a look at this. We'll just wrap this in a div. We'll go to hero, call in that component.\u003C/p>\u003Cp>Let's see what this is starting to look like. Alright. Okay. Okay. We got a little something going on here.\u003C/p>\u003Cp>I only succeeded in moving one thing to the left. So let's take a look at that hero component one more time. Alright. So this has got some type of gradient blur attached to it for the images. Can we lift this?\u003C/p>\u003Cp>Oh, yes, we can. Netflix. Love building on the fly. Thank you. Netflix.\u003C/p>\u003Cp>What else do we have? The overflow hidden. The the the announcing. Do we even need this? Let's let's just scrap that part of it.\u003C/p>\u003Cp>And we'll just show watch chapter 6 now. And I still can't copy this stuff. Come on, Netflix. Copy. Copy selector, copy element, edit as HTML.\u003C/p>\u003Cp>All right. So we'll just pick this up and throw it here. And we got the My Little Pony logo, so we'll add in Nuxt. We'll just add in an image tag because I don't have that domain configured. Image source.\u003C/p>\u003Cp>Alright. Class. I don't know. Maybe height 24. And an image tag, welcome back to Equestria Tech Center.\u003C/p>\u003Cp>This is not gonna be Tech Center. Okay. We're getting somewhere. How do we dim this image? Right?\u003C/p>\u003Cp>Brightness 50? Oh, wrong image, Brent. Brightness, 50. Maybe 25. Oh.\u003C/p>\u003Cp>Can we do a percentage here? Get, like, a a nice value. Okay. Alright. So we're getting closer.\u003C/p>\u003Cp>He likes it. Alright. Get started. Let's say watch now. Okay.\u003C/p>\u003Cp>Let's remove that one. Alright. So at least we got some type of header here. This would be a NuxLink. And, I'm trying to think of how we're gonna serve this content.\u003C/p>\u003Cp>Right? We would do a page. Let's add a new folder for this. This is gonna be what does Netflix do as far as routing? Right?\u003C/p>\u003Cp>If I open this up, episodes and info. Okay. So is it just like a genre? Is that how how the URL structure is? Because you watch Masha's spooky stories.\u003C/p>\u003Cp>So we got the genre. If I look at the URL again, yeah, it's just like Netlify Kids, and then there's a query param. Alright. So it actually appears to be inside the the actual application. Right?\u003C/p>\u003Cp>So we're just appending, like, a query per annum to it. Let's come back to that step. Let's actually render out some titles first, though. Right? So we go back to our page.\u003C/p>\u003Cp>Let's create a new component. What what do we gotta call this? Content grid? Sounds great. Vcomp ts.\u003C/p>\u003Cp>Okay. So now we've got a a Vue component. Let's do some data fetching. Right? We will do something like this where, we got constant data equals use async data.\u003C/p>\u003Cp>So this is the built in Nuxt data fetching. We give it a key. Let's call it content grid, comma. Alright. And then we're going to return something.\u003C/p>\u003Cp>So in this case, we're gonna use our directus composable that I've got inside this. But if not, I could import the Directus SDK, fire that up, but, we're on the on the clock here. So alright. So we're gonna read our items, and we are going to read titles. So and then we're gonna get, is there any specific thing that we wanna render out?\u003C/p>\u003Cp>Let's just leave our options blank for now. Right? So we got our data. Let's just go in and make sure we're getting that data back. Sounds great.\u003C/p>\u003Cp>I don't see it on the screen. Why not? That's because we're not actually including it in our index page. So we'll just do content grid. Great.\u003C/p>\u003Cp>Okay. So at least we could see a few things here. That's great. Do we really need anything else to render this grid? Probably not, actually.\u003C/p>\u003Cp>This shows, like, the viewing data. We're we're definitely not gonna get to that. We got 12 minutes left. Let's just see if we can actually render something out. It shows something in a modal.\u003C/p>\u003Cp>All right. So we got our content grid. Great. Cool. Let's go in.\u003C/p>\u003Cp>I'm not sure if Tailwind has a great looking component for this. Gridless. Kind of. Yeah. Let's just create something on the fly.\u003C/p>\u003Cp>Right? So we'll go in. Alright. So that's the wrapper div. Then we'll do, we would probably fetch this via genre as well.\u003C/p>\u003Cp>Again, running short on time. Alright. We are going to do a, let's just keep this simple. Right? Add add a little bit of padding to the sides.\u003C/p>\u003Cp>We'll give this a grid. Grid calls, 5 across the screen, 6 across the screen. And then we'll do, let's do a, what, title card? And we will pass so we'll do some v four action here. V 4 title and data.\u003C/p>\u003Cp>The key is gonna be title dot ID. And then we have our, what, We're going to pass a title, title equals title. All right. So now we're going to create a new component. Call it title card dot view.\u003C/p>\u003Cp>And we will define some props. So props equals define props, Title object is prop type. Haven't set up our types here even though we're still using TypeScript. Alright, so we've got a card. I know Telen has like a card definition.\u003C/p>\u003Cp>Actually, the Nuxt UI library has a card as well. Let's just use it. So we got a card. There's a card. U card is the name of this.\u003C/p>\u003Cp>U card. And what do we have inside the card? We have, I don't know if these actually need to be h tags or not. We'll just use a p tag. Got the title dot name.\u003C/p>\u003Cp>If I can actually type this. Right? We've got an image for the card. Source equals title. No, no, no, no.\u003C/p>\u003Cp>We're gonna use Nuxt image because I have it preconfigured. And I'll just show you that really quickly. If I go to Nuxt Config, ba ba ba ba, where are you? Where are you, mister Image? Image.\u003C/p>\u003Cp>Okay. So Nuxt Image Directus is a provider here. We just give it a base URL that communicates with our Directus instance and allows us to do image transformations on the fly, which is really nice. So we got source title dot thumbnail. K.\u003C/p>\u003Cp>What do we do? Like, what? With full height 24 maybe? Sounds good. We got a title.\u003C/p>\u003Cp>Let's make this font bold. I'm assuming that's what it looks like. Oh, no. It's actually just a card. Right?\u003C/p>\u003Cp>Alright. Let's see what this looks like. We'll go back to our index page, content grid. Why don't we have our content? Why is content grid not rendering anything?\u003C/p>\u003Cp>Do we save it? What's going on here? Dev server. Why are we not rendering? Title card.\u003C/p>\u003Cp>Content grid. There is no data. What are we doing there? We just had our data. Right?\u003C/p>\u003Cp>Am I being silly? Pre data. Is that not showing? Did I break something? What is going on?\u003C/p>\u003Cp>Use, async data, content grid, titles, read items. Where did you go? Did I not just have this, like, a minute ago? Title. Title equals title.\u003C/p>\u003Cp>Hydration mismatch. If I look at content grid, like, we should have data. Right? Let me just look at the Nuxt fetching. Sometimes I even I use Nuxt all the time, and I'm not sometimes I have to go back to the oh, we gotta wait.\u003C/p>\u003Cp>Yeah. I don't know how that worked the first time. Right? Next image not showing. Okay.\u003C/p>\u003Cp>So we're missing some images there. Why are we missing images? So we are missing images because, let's do let's add our grid back. Grid calls 6bg grid. Do we need an actual BG?\u003C/p>\u003Cp>Okay. Maybe grid calls 4. That's fine. How are we doing on the time? We got 6 minutes left to actually render something up.\u003C/p>\u003Cp>Px8py12. Just pad this out a little bit. Alright. What's next? Our our images are not displaying.\u003C/p>\u003Cp>Right? How do we fix that? It is actually gonna be inside our Directus instance. So it is a permissions issue. Pretty sure if I were to take a look at our network requests, look at it like images here, you have 403 forbidden.\u003C/p>\u003Cp>So, I would go into not my docs. We'll open up the Directus instance. Go to 8055. And I somehow managed to log myself out as well. Alright.\u003C/p>\u003Cp>We go into access control. We've got our content here, but then we're gonna go in and allow access to the direct us files collection. So that way we can actually serve those images on the front end. Okay. Great.\u003C/p>\u003Cp>Maybe title card wasn't a great idea there by using the Nux card. Right? Let's just call it div. Oh, forgot to close that. Alright.\u003C/p>\u003Cp>So there's the diff. This is going to be Object, Cover, Trash Truck Christmas. Maybe we need to make that a little higher, 32. Alright. How we doing on time?\u003C/p>\u003Cp>4 minutes. Can we get this to actually render? Alright. Button at click. Alright.\u003C/p>\u003Cp>Quick and dirty. Let's do this. We're going to add a composables. Actually, we could just pass this up as well. Alright.\u003C/p>\u003Cp>At click, we're going to define some omits. So omits equals define omits. We're gonna tell this select title. Select title? I think that's the syntax.\u003C/p>\u003Cp>Alright. Alright. Select title. Emit. Select title.\u003C/p>\u003Cp>And we're gonna pass the what? Prop no. It should just be title dot ID. Is that right? Or does that do I have to wrap that?\u003C/p>\u003Cp>No. Where are we getting here? Select title does not exist. Let's just call this function. On click, emit select title, props dot title, on click.\u003C/p>\u003Cp>Cool. Okay. And then inside our content grid, let's do this where we've got a modal. K. And we've got is open.\u003C/p>\u003Cp>That's false. Select title. And then what are we gonna do for the title? Let's do title, maybe content. Right?\u003C/p>\u003Cp>Content ref equals null. GitHub Copilot here for the win on this one. Right? Do we have a video component? I don't have a video component.\u003C/p>\u003Cp>So we're just gonna do, what, an iframe, class, aspect, style. Let's call it video. We'll just add a quick style here. Dot video aspect ratio 16 by 9. Alright.\u003C/p>\u003Cp>The well, the source is gonna be the content dot YouTube URL. Is that actually gonna work? I think I've got do I have some strings in here? Do I have a video content? I don't.\u003C/p>\u003Cp>Alright. Let's rely on AI here. Generate YouTube embed. Okay. So that returns the video ID.\u003C/p>\u003Cp>Let's see what AI comes back with. YouTube embed video ID. Okay. Great. Alright.\u003C/p>\u003Cp>So there's gonna be our source. We'll say generate YouTube video embed URL. Oh, nope. That's YouTube embed URL. Like, somewhere in the starter, I may already have this.\u003C/p>\u003Cp>Generate YouTube embed URL, and that is gonna be the content dot YouTube URL. Alright. So what am I missing? I'm gonna need to close that. Okay.\u003C/p>\u003Cp>And then on our select title, right, we need to fetch the content for the title. Alright. So that's gonna be what? This is gonna be an async function now. How are we doing on time?\u003C/p>\u003Cp>We have hit time. So we ran out of time to actually flesh this out and render this content in a model, but I just wanna try it. Let's just pursue it really quickly and see where we're at. Constant. We are going to we don't need to actually use async data for this.\u003C/p>\u003Cp>We're gonna fetch this on the client side. So let's do constant title equals wait. Use direct us, read items, title. Read item. Is it gonna be a title?\u003C/p>\u003Cp>We're gonna fetch through the we got the root level fields. Let's take a look at that data model again. Right? Local hosts. Alright.\u003C/p>\u003Cp>So we got titles, then we've got content? Alright. So root level, we'll go content dot that. Select title. Title has already been declared.\u003C/p>\u003Cp>Oh, okay. Where do I have that at? Oh, I've already given it the title. Content. Alright.\u003C/p>\u003Cp>So if I click that, is it actually making the request? Select title. Oh, and then for the title card at select title, we're going to select title. Alright. So I can see that's making the request.\u003C/p>\u003Cp>I get the data here. There's our content. Do we have the YouTube URL? That's great. For some reason, it's not throwing the modal window.\u003C/p>\u003Cp>Right? Why is the modal not opening? Cannot read properties, undefined of YouTube URL. Alright. So we probably wrap this in, like, a computer or something.\u003C/p>\u003Cp>Selected content equals computed value. Alright. And this is going to be if selected content and we'll have selected content. Is that why? Alright.\u003C/p>\u003Cp>So it's actually throwing the modal, but why isn't that working? It's because I'm not passing the right stuff. Right? So it should be dot content dot YouTube URL. Is that gonna work?\u003C/p>\u003Cp>Where are you? Where are you? Modal. Title card. Where's the content grid?\u003C/p>\u003Cp>Our selected content. Content dot value. Oh, duh. Gosh. Forgetting to set the content.\u003C/p>\u003Cp>Content dot value equals content. Let's try it now. No. Still new. Okay.\u003C/p>\u003Cp>So I hate to waste everybody's time. Content dot oh, fetched content. Let's just try this real quick before I give this up. Equals fetched content. Holy moly.\u003C/p>\u003Cp>And there it is. Right? We have a Netflix clone. So a little after time, we made it actually render something, which is a win. Kudos to the Netflix team.\u003C/p>\u003Cp>Right? The truth of the matter is that it is very difficult to build Netflix in an hour. So I hope you enjoyed this episode. Like, what would happen next for me? Basically, continue to flesh this out, get the UI working and and wired up correctly.\u003C/p>\u003Cp>You know, if I wanted to copy Netlify or Netflix directly, I would probably make sure that when I loaded a title that was getting appended to the the actual query string in the URL. You might set it up on a different route that renders a model. But next steps would just be basically continuing down the route of fleshing out the the UI side of it and, you know, adjusting the data model inside our back end as needed. So that's it for this episode of 100 apps, 100 hours. The outcome, maybe we give it one thumbs up.\u003C/p>\u003Cp>You know, I'm sure Cisco and Evert would probably not approve. So I'll catch you on the next one. See you.\u003C/p>","Alright. Welcome back to the next episode of 100 Apps, 100 Hours. I'm your host, Brian Gillespie, developer advocate at Directus, and I'm super excited for today. We're gonna answer the question that has been keeping you up at night, can you build Netflix in an hour? You've probably not been keeping you up at night. But the truth? You can't handle the truth. I can't resist throwing in a good movie quote here for this one. So we are going to try to rebuild Netflix in 1 hour or less or publicly fail trying. Those are the rules of 100 apps, 100 hours. And then the second rule of Fight Club is use whatever you have at your disposal. So how are we going to achieve this? We are going to do a little planning, then we're going to do a little building. But just to get eyes on what we're going to build, here's Netflix. You know it. You've seen it. We are going to try to rebuild as much of this as possible in 1 hour. So that means the back end, that's where we'll start. Then we'll work on the front end and try to get as close as possible to the original Netflix or, my own spin on it as possible. So what do I have prepped ahead of time? I have a Nuxt application ready to go. I've also got an instance of Directus. Directus is what we're gonna be using for the back end. I've got it spun up. It is totally blank now and before we get started, I'll show you just so you know I'm not cheating. So we'll go to admin, example, we'll key in a quick little password. If I can actually remember what the password is. There we go. Okay. It is a totally blank instance, and if we fire up the database, again just to prove it, we've just got our directus collections that it adds for us automatically, so just the metadata. But as far as our actual data, there is not. So with that out of the way, I'm sure you guys trust me anyway, but let's get started. Right? We got 60 minutes on the clock. Boom. Away we go. Alright. So before we even get started, you know, maybe this is not the Netflix clone. This is, what are we going to call this, right? Give it a name. How about Dev Flix? Dev Flix, yeah, this is essentially what this could be. So this is Dev Flix, let's strike through this. This is what we're going to be building. And anytime I build a project, I I like to do some concepting first, maybe some diagramming, just so my brain is warmed up and I understand what's going on. So we'll just draw some boxes here. Let's walk through the functionality or do we even need to cover functionality, right? We all know what Netflix does, but we've got a, browse a catalog of titles. Those could be a movie or a show. What else? View that content. View stream videos. Right? Stream Videos once title is selected. Pick From different categories. Yeah, I don't know. This is Netflix, right? We all understand the functionality involved. Alright. So let's start working on the data model. Right? One of the ways that I will usually try to deconstruct application is just open up the JavaScript console and or the developer tools, and not necessarily the console. And then take a look at, like, the network requests that are coming in to see if I can understand what's going on behind the scenes. You know, I don't know that Netflix does Netflix have an API? Does it close its okay. So they closed their public API. But if we look, we could see there's a ton of calls going out to a player, but I see here's like some GraphQL stuff, some videos, artwork. I'm not really sure if this is gonna get me anywhere behind the scenes. They spend a lot of time on security and things like that and, you know, performance. So maybe they're doing some server side rendering and and things like that here to actually display this content. No worries. Right? But let's walk through and and just kind of think through things. Right? We've got what do we have inside Netflix? We have a, like, a title, like a content title, or, we could call it content, but that doesn't seem right. It could be a movie or a series. So let's just call it titles. Right? These are movies, series. There's a name for that, description, probably like a rating. You know, there's a lot of things. So we just wanna capture, like, the core stuff here. Alright. So we got titles. What else do we have as we go through? If we look at My Little Pony here on the screen, we've got our episodes and seasons. So if it's a show, we've got a season. Season has a number, probably, what, a release date for the season. And then it's gonna have a title ID and probably episodes. Right? Episodes. Now behind the scenes, I don't know exactly how Netflix Netflix has set up, like, their video and, you know, like, the the encoding and and streaming and all of that. I'm assuming attached to each title or each season, each episode. Alright. What do we have? Oh, sometimes you just accidentally push command q and totally destroy all of your progress. Alright. So how are we gonna structure this? Right? This is probably the most important thing to get right. Each episode is going to have a episode number, name, description. It's gonna be back to, what, a season, season ID. And then there's almost like another relationship here, which is just the actual content. So this is just as, like, the actual file. We'll probably just use, like, a YouTube URL in this case. Okay. So these are our kind of main collections. You probably also got things like ratings, users. Directus gives us that out of the box, which is really nice. So we've got Directus users. And, you know, we could have something like viewing history as well. User watch history. You know? We're getting really deep. Like, the main functionality is just up here where we have these items. Right? These are the core pieces of this application. Alright. Shoot. I forgot to start the clock. How long have we been doing this? Let's just give, like, 55 seconds or 55 minutes. Or I did start the clock, but we deleted Figma. Right? So let's handicap it a bit more. Maybe 54. Okay. Alright. So, we'll go back and look at that during editing, but, yeah. Sorry. Mistakes happen, especially when you're against the clock. Alright. So now that we've kind of got a rough idea of how we're gonna structure this, I like to work back end first because when I'm building my front end, I love having access to the actual data instead of lorem ipsum, dipsum, and then I have to wire everything up later. So with this stack, Directus and Nuxt, I can quickly scaffold my back end and get instant rest and GraphQL APIs, and then plumb that to my front end as I'm building the front end. So, it's a a really great workflow for me. I hope it works well for you. Give it a shot. Alright. So let's go in and create our first collection. We're gonna call this titles. And I'm sure somebody will tell me why this is wrong, why it is not. So Directus gives us a couple of system fields that we can add. These are just little helpers, like, date created is automatically populated with a time stamp. User created is automatically populated with a logged in user that created that. Do we have a sort for those? And I can already tell I forgot one big one, right, which is gonna be genres or categories. Like, we've gotta have some kind of taxonomy for those. Alright. So we've got our title. What is the type of this? Right? So it's either gonna be a movie or a series. Let's use our radio button interface for that. So this is gonna be the type. The first one will be movie. So it's either a movie. Let's say we default to movies. This is gonna be series. And and Directus also has built in translation strings, which is another nice thing that I could go in and create these translations where here's the key. Let's call it what? Series. Right? Series. And in English, this is gonna be let's just capitalize this field anytime we render it. Right? Cool. Alright. So then I could do something like this where I have dollar sign t and do series, and that will automatically translate that for me based on the strings that I've got set up, Which is really nice if you've got users across different languages inside your Directus instance inside the back end. Alright. So we got the type is either a movie or a series. Let's add an input for the name of this title. And I can even go in and add like a little helper. What's the name, in this notes section? What's the name of the content of the movie or series? Right? Or series. Great. What else do we have? We got a description. And let's just go for, like, a rating. So we got a drop down. Let's call it rating. Or, you know, we could probably have, like, a ratings collection here. I was probably thinking this is like reviews, but, let's just take a look and see. Right? This says this is TBY. Do we wanna be able to query by that? Right? So in that case, let's do a many to 1, and we'll call it a rating. And we're gonna have Directus create a new collection for us in the background. So everything that's going on here, I feel the need to explain this, Directus is actually mirroring this inside my database. So as I create these new fields, as they're called inside the Directus application, it is actually mirroring those to my SQL database. So we'll call this ratings. And if we go to advanced mode just to take a look at this, maybe we zoom back out a little bit. But I could see that Directus is telling me that, hey. We will create this inside your data model. Great. So, it's gonna create that for me. And then we've got a mini to 1 relationship. Great. Okay. So we got the rating. And now if I look, we've got our ratings collection created as well. Alright. So we'll go in, let's add a rating, the name for the rating. I'm not sure if that's what these are actually called or not. And let's just create a couple of these. Right? Like pg13, TV y. Is that what I saw? TV y. Yep. TV MA, rated r. Yep. Alright. So we get the the picture. We could call it PG, and then we have G. Right? Some of my favorite movies are still g rated movies, to this day. Of course, I'm a father of 3 as well. Alright. So we go back to our data model. Let's build out the rest of this. We've got our titles. So if we are creating a title now, like My Little Pony, now we can see what the status is. Is this a movie? Is it a series? What's the name, description? And we could pick a rating that we can then update through the system. And let's just adjust the display template for this because I wanna show the name anytime we're referencing that rating. Alright. So I'll zoom back in just a bit, and we'll build out our next piece of the puzzle here. Let's work on seasons. Alright. So we'll automatically generate a UUID. Maybe we wanna store who is the user that was updated this just so we can play the blame game later. Cool. Alright. And then we've got a number. So let's do an integer. We'll have the season number. Okay. And inside Directus, I can also make that the sort field. Where are you, sort field? Alright. So we'll choose the sort field. The season number is going to be the sort field. So whenever we reference this, it will sort by that number automatically. What else do we have? Do we have a description on the sort number? I I don't know. Release date, you know, this theoretically could be on the actual episodes as well. But Netflix does like to drop a season at a time, so maybe we store it here as well. Let's just use a time stamp so that it saves the the time zone value as well. Alright. What else? We have a title ID. Right? So we have to create that relationship back to the title so we can fetch that data. So this is gonna be a mini to 1. Right? A season can only belong, like, to a a single content title. So if if the show is My Little Pony, there's 5 seasons, all those seasons belong to 1 My Little Pony title. So we could call that title ID or title, I prefer less verbose, but it doesn't really matter here. Directus is going to serve up that however we create it. Alright. So we've got that. Let's add the one to many relationship. So we're creating a many to 1, but on the reverse side, a single title could have many seasons. So let's go ahead and create that relationship as well. And for the season we'll use the oh, for our many to 1 we'll use the name of that and then we'll do related values. Okay. So we got the title, we've got the number. You know, we could have a description for this season if we wanted to. You know, there's probably even, like, some collateral or trailers or or something attached to this. We won't worry about that right now. Let's save this, and then we'll go in and create episodes. So in our diagram over here, we probably should have had episodes to begin with. Alright. So same thing. I'll I'll just add my system field. So if anybody updates this information, we've got it. Is this episode published or not? Yes or no. And then we'll give this a name. What's the name of the episode? We probably also have a number for this episode as well. Episode number. And, you know, something like this, you may even call, like, episode number just to make sure everything is very clear, but I'm all for making things difficult. So we will add a description. And now we'll link this back to the season. Right? So we're going to use that many to one relationship again, and we're going to link this back to a season. The related collection is seasons. And I'm going to open up advanced field mode here in Directus, and I guess I could actually use my little mouse pose tool for you guys so we can get an idea of what's happening here. So I'm gonna add the reverse relationship back to seasons. So one season could have many episodes, 1 episode has or many episodes have a single season. Think I explained that right. Sounds great. Alright. So now we've got titles, we've got episodes, we've got seasons. If I look, we've got titles. So let's go in and create a new title. Right? So we'll just drag and drop Netflix over here. And let's just work on this. Right? My Little Pony, Make Your Mark. Alright. So this is My Little Pony, Make Your Mark. Alright. Maybe there's a hyphen there, a dot, I'm not sure. That's the season description. Right? Where do we see all episodes? Alright. So here's the there's kind of the the generic description that apparently Netflix does not let me copy. Alright. So welcome back to Equestria, where pony magic is everywhere. Who doesn't love pony magic? Alright. There's our description. This is TVY. Alright. And I can already see we've left out a couple things. Right? We've left out some thumbnail images, probably on each episode, each, title as well. But we'll go in and create a new season. They're calling this chapter 1. So we probably have a name for the season as well that we can add. Let's just say the release date is today, and we'll start adding some episodes. Right? So we got Make Your Mark. That's episode number 1. Something's wrong in Equestria. And now we're actually watching Netflix. How do I go back? Okay. Alright. Is there a way to actually I I guess they disable that. I'm just gonna use Raycast here to generate descriptions for this. Alright. So we'll say this is published. Alright. So at the very least, now we have some stuff that we can work with and fetch via the API that we can render on the front end. I'm gonna go back really quickly and just add a thumbnail for this title. And we could even do that for what, the episodes as well. We probably got, like, a thumbnail. There's probably also, like, a teaser. And while we're at it, let's just go ahead and try to finish out what we were building on the back end. Alright. So then we've got the actual content. So creating this separate collection allows me to standardize this across episode or, like, TV and movies. So, let's go in and create the content. You know, this could be video, I guess. Yeah. I'm not sure. Let's just call it content. And I'll probably figure out a better way to do this at some other point. Alright. So we got the content. We've got a just use a YouTube URL for this. We're gonna cheat a lot. But this could be like an actual file that we uploaded to our back end here that we serve. Again, I don't want to get into, like, the streaming stuff because I like efficiently serving video at different bandwidth, at different resolution. That's a whole ball of wax. I'm sure there's a great API for it. And Directus will manage some of those assets for you in a nice way, but it doesn't do video encoding and things like that that you need to deliver those streams effectively. Cool. So we've got, what, a YouTube URL. There's the content. This is gonna have a title associated with it. Titles. K. And then there's probably what? Episode as well? Episode. Titles. Okay. Alright. So we got a title. We got an episode. We probably should have created those reverse relationships, but we could go in and do that now. Alright. So we'll call this the content. That'll be our content collection. Episodes. And now we'll just create the reverse. Uh-oh. What am I doing wrong? I'm doing a mini to 1 back to that when, actually, let's just go in and I'm gonna delete these for now just to make sure we set these up correctly. Alright. So I'm gonna go let's go to the titles. We'll do a many to 1. We'll call this content. We'll go to the content collection. And here, I am going to go into the relationship. We'll add this to the title. Okay. So we'll add that reverse one to many relationship. We'll call it the title. I think it is going to be an array. I guess you could have multiple pieces of content attached to any one item, but no worries. Content. And then we're gonna have episode. Okay. Okay. Cool. Cool. Cool. Alright. What's next? Right? We probably need some genres. Let's go in genres. And if there could be multiple genres per piece of content, I assume, like, could be drama and horror or drama and faux drama. If you live in my household, I've got plenty of drama. So we'll give this a name. And then let's go in, and we would create a junction table between titles and genres. Right? But, again, one of the nice things here is Directus is gonna do a lot of this for us. So we could call this genres. We do genres as the related collection. And if we look, we go to the advanced mode inside Directus, we can see that there's gonna be a junction collection or a junction table in our database created. Do we want to be able to query by genres? Maybe we don't need that. Do we add a sort field? Probably not. Okay. So now we've got genres. We've got thumbnail. We got a lot of different items set up in this database. Right? And we're closing in on 30 minutes. We probably need to start building some UI soon. But I do wanna just open up the database itself and and take a look at what's going on now. So you'll see these direct us tables. But if you look, you can also see our our actual tables that we created, like episodes and genres. And if I look at the data, here's the episode that we created, make your mark for My Little Pony. So this is great. Like, anything that happens inside the app here, Directus is mirroring that to your database. Likewise, if I go in and, there's very robust access control here. I'm just gonna enable this as a public permission for all of our content, which, would probably not be a great thing security wise, but I just want to show you this. We'll go into local host 8055, which is my back end. And if I go to items and I go to titles, boom, I'm gonna get My Little Pony. Right? Now, this is not super helpful because we're just getting you know, like, let's say I I just wanted to make a single call and get all my content. I love Directus for that because it gives me an interface via REST that feels like GraphQL. So I get the benefits of GraphQL and then I can go in and do something like this, like, hey, I just want the ID, I want the type, I want the name, I want the description, Boom. And it will give me exactly what I asked for. Now, the other thing that I can also do is go in and if I want something like seasons, and I I could use a wildcard to grab all the root level fields of the the next one, right, but I can get all of my related content in a single API call. That's super powerful, super helpful. Obviously, like, the deeper you go, the slower the queries on the back end are gonna be, but it is still very speedy. And, obviously, I don't have a ton of data here. So we got seasons dot star, and then we could do seasons dot episodes dot star. And that should give us the episodes. And then once we add content, we could go even deeper and say something like seasons dot episodes dot, content dot star. Now there is no content at this point, but if there were, it would come back. Great. So that is just one way that one of the major things that I really love about directives. Alright. So we've got some stuff going on. Let's go in and populate a few more titles. Right? We've got a thumbnail. I don't think they're gonna let me lift this. So let's just do the old screenshot trick. Upload that image. Click to browse. Should actually just be able to drag and drop there. Okay. Let's create a new content for this. We'll call it YouTube. Pull you up, My Little Pony. And again, I I don't support any of this. Make sure anything that you are doing is okay legally. We'll just copy this. K. Great. We've got a movie. Actually, this is a series. Right? But one thing I noticed here is if I'm switching between movies and series, we should probably actually not show our seasons. Right? So how do we actually adjust that? Well, if we go into our titles, Directus has the ability to do, relational or, I'm sorry, conditional fields. So I could go in and for seasons, we could do something like this where we hide this by default, and then we come into our conditions tab. And if type equals series, so then we build a rule. This is just a description for this. We'll do type type equals Series, then we are going to make this not hidden. Right? And we'll show a link to the item. Did I get that right? Yep. Okay. Alright. So let's test this out just to see what's up. Let's go in and add another one. Right? And now where I have movie selected, nothing. But if I select series, now we're seeing that field. It doesn't change the underlying database structure or anything. It's just hiding that when I'm creating the content. But I could even add validation around it if I wanted to. So if we go in and we add a movie, let's see if we've got a movie. I could switch up here to the top to movies, and we open this up. We've got a trash truck Christmas. Christmas. Alright. This is TV Y, made for TV, made for TV goodness. Alright. We will create some new content for this. Where's our handy YouTube? Trash truck Christmas, full episode Netflix Junior. This looks like an official channel. Yeah. Okay. We'll see. So we got that. We've got a thumbnail image. Again, I'm not even gonna try to lift this from oh, actually, I I guess I could. Copy image address. Is that available? On the web? Okay. Cool. So we'll just lift these images directly. I can import that file. Great. There that is. And I could, you know, maybe potentially find their GraphQL, stream for this that allows me to pull all this in, in a a more timely fashion. But, okay. Genres. Right? Let's create a new genre. What are we calling this? This is kids TV. There's a new genre. Okay. We'll create a new kids and family movies. And this will be TV cartoons. Great. Okay. So we got a movie. Let's switch this back to a series. Right? Lots of series because we've got seasons attached to it. Got some content for it. Is it is that gonna be right? Actually, it's not, is it? We're going to go back to My Little Pony. We'll go into the seasons. We've got episodes, and we should have content for that episode. Cool. Alright. So let's just see what this all looks like. Great. I could even go in and adjust this to, like, something akin to Netflix if I wanted to where I could see, like, the thumbnails of all these different titles. So give it a name. The subtitles could be what? Description. What else do we have? Or it could also be can we get genres in here? Genre genres ID. Let me get the name of the genres. Cool. Alright. So we got a couple titles. We are what? We're pretty I'm pretty happy with where the back end is at at this point. You know, we could really go through this in a lot more detail and create something like actors, you know, the cast and crew, director, things like that. And those would probably all be different database tables. But for the sake of having something to display at the end of this next 28 minutes, let's just get to work on the front end. Alright? Cool. So we will let's put you back over here, Netflix. Close you. We'll close our database. Let's pull up our Nuxt application, and I'm just gonna swap these around. No real rhyme or reason. I just like my text editor being on the right side. Alright. So now let's pull up our Nuxt application. Oh, this thing is a beauty, right? So, I've just got a preconfigured, like, a Directus Nuxt module set up that basically just wraps the Directus SDK with a little bit of auth. You know, we may not even dive into that. But, and then I've got a use direct as composable that allows me to make requests easier. There is a a little bit of setup work that I've done here, but it is really just, one of our boiler plates HSC OS that I have taken and stripped back for the purposes here. Alright. So we go into our index page. Alright. So we let's look at, the homepage here of Netflix. And we've got kind of this setup of a header. We've got a grid of titles sorted by genre. Alright. So let's dive into this. The first thing that I'm going to do, just because we are short on time, is take a look at, Tailwind UI. Advanced. What's going on here? Okay. Let's just look at Nux UI as our our library here. Something wrong with my laptop at this point. Alright. So inside this boilerplate that I've got set up, I've got a little, I've got I've got this Nuxt UI library, which is kind of built on Tailwind as well, that gives us some nice things like modals, some of the trickier components to actually build. Right? So we go in. Let's try Tailwind UI again, now that I've refreshed a little bit. And, alright, so what are we looking for? Let's grab a, like, a header from Tailwind UI. Alright. This looks pretty good here. So we've got the view version. I'm just going to copy this down. Alright. And let's go in and create a header, error component header dot view. Alright. Are we gonna have a logo for this, right? We've got our logo. Can we pull that up? Will this let me copy as PNG? Can I export this as JPEG? Nope. Let's just do a new Figma file actually instead of FigJam. Can we paste that? If we copy, Can we paste? Devflix. Yeah. Alright. Let's give it some, like, type of little icon treatment here. So we'll add a real original logo here. Looks like a play button. We'll change the fill to red. Right? Okay. And maybe we, what, let's wrap this in. Let's give it a little oh, yeah. I'm I'm gonna be giving design courses after this as well. Alright. So we'll take this. We'll export to SVG. Let's just do logo. Great. Alright. So we've got this header component. If we go back to Netflix, what do we got at the top? We've got, home. That'll be our index page. This is for our navigation. Right? So we've got TV shows. Alright. We got movies. Great. Okay. Cool. I don't have Headless. I don't have heroicons in here, so let's just comment those out for now. Bars 3. I'm sure that'll throw a fit when we actually take a look at this. But cool. Alright. Now I'm gonna create a new file called logo dot view. And we're just gonna copy this as, what, SVG. Paste here. Nope. I need to wrap that in a template, though. Alright. There's our SVG. Now inside our header component, I'll go back up here to where we have Tailwind UI's logo. And I'm just going to call that logo component that we just had. Don't need a source. Don't need an alt tag. Well, we probably do, but not going to have one. 100 apps, 100 hours. Okay. So we have a header. We're not seeing it here. What I can do is adjust the default layout. And let's do something like this where we have BG gray 900 and probably text white since we're kinda going on the Netflix theme. So this is the layout inside Nux, which is a feature that I really like about Nux, is the ability to set up a default layout. And boom, we've got our DevFlix header looking nice. If we go back to that header, probably wanna swap these a tags or a Nuxt link and change this to 2. And we could change this to TV shows and movies. Right? We just have to create a route for those. And I'm not sure exactly how far we'll get because we have, what, 21 minutes remaining. But we'll we'll see how far we can get. Alright. So if I go into the header, we need to update the details for it as well, like bg white. What do we what does Netflix look like on the header? It's just kinda transparent until you scroll down. That's a nice effect. I don't want to set up anything there. So let's just cheat. We'll call it bggray900 again. And then that's gonna mess up a lot of our text, I believe. Right now, we can no longer see all of that. So we go to our text. Let's look at anything with text gray. Can we replace that with we could probably do smaller values, like text 200. Again, I love Tailwind just because it enables me to not have to write custom CSS for everything. Like, it it does take some time to learn the conventions, but but being able to not write CSS, I like custom CSS, just helps me speed along. Now I know it's not for everyone, so don't let me tell you that it is, But I enjoy it. Alright. Can we get our stuff? Okay. Alright. That's probably because we're missing an icon for our navigation over here. So what is this? This is hidden. Flex LG. Let's hide that at, like, just a smaller breakpoint. Yeah. Okay. Cool. Alright. So we got home. We got TV shows. We got movies. We can log in. Great. Now let's actually work on our page. Right? If we go in, let's actually render some stuff out. Alright. So if I look at Netflix, we've got a, like, a header component or, like, a hero image. So we go for our hero sections. Hero. Which one of these kinda looks like Netflix? Kind of this one? Kind of. I mean, this one has a background image. So we could take that. Okay. So we're not gonna copy this header piece, but let's just go in. I'm gonna copy this part. We will create a new one called hero dot view. I've got a just a couple of snippets already set up for me inside Versus Code to make creating Vue components a little easier. And k. We're good there. Watch chapter 6 now. Is this just kind of a rotating thing, probably based on, like, the last thing that you watched? Instead of doing that, let's do something else. Data. Alright. Alright. So this is gonna be justified center. We don't wanna do that. We wanna do does it justify start, I believe? K. Oh, we'll add this to our index page. So let's take a look at this. We'll just wrap this in a div. We'll go to hero, call in that component. Let's see what this is starting to look like. Alright. Okay. Okay. We got a little something going on here. I only succeeded in moving one thing to the left. So let's take a look at that hero component one more time. Alright. So this has got some type of gradient blur attached to it for the images. Can we lift this? Oh, yes, we can. Netflix. Love building on the fly. Thank you. Netflix. What else do we have? The overflow hidden. The the the announcing. Do we even need this? Let's let's just scrap that part of it. And we'll just show watch chapter 6 now. And I still can't copy this stuff. Come on, Netflix. Copy. Copy selector, copy element, edit as HTML. All right. So we'll just pick this up and throw it here. And we got the My Little Pony logo, so we'll add in Nuxt. We'll just add in an image tag because I don't have that domain configured. Image source. Alright. Class. I don't know. Maybe height 24. And an image tag, welcome back to Equestria Tech Center. This is not gonna be Tech Center. Okay. We're getting somewhere. How do we dim this image? Right? Brightness 50? Oh, wrong image, Brent. Brightness, 50. Maybe 25. Oh. Can we do a percentage here? Get, like, a a nice value. Okay. Alright. So we're getting closer. He likes it. Alright. Get started. Let's say watch now. Okay. Let's remove that one. Alright. So at least we got some type of header here. This would be a NuxLink. And, I'm trying to think of how we're gonna serve this content. Right? We would do a page. Let's add a new folder for this. This is gonna be what does Netflix do as far as routing? Right? If I open this up, episodes and info. Okay. So is it just like a genre? Is that how how the URL structure is? Because you watch Masha's spooky stories. So we got the genre. If I look at the URL again, yeah, it's just like Netlify Kids, and then there's a query param. Alright. So it actually appears to be inside the the actual application. Right? So we're just appending, like, a query per annum to it. Let's come back to that step. Let's actually render out some titles first, though. Right? So we go back to our page. Let's create a new component. What what do we gotta call this? Content grid? Sounds great. Vcomp ts. Okay. So now we've got a a Vue component. Let's do some data fetching. Right? We will do something like this where, we got constant data equals use async data. So this is the built in Nuxt data fetching. We give it a key. Let's call it content grid, comma. Alright. And then we're going to return something. So in this case, we're gonna use our directus composable that I've got inside this. But if not, I could import the Directus SDK, fire that up, but, we're on the on the clock here. So alright. So we're gonna read our items, and we are going to read titles. So and then we're gonna get, is there any specific thing that we wanna render out? Let's just leave our options blank for now. Right? So we got our data. Let's just go in and make sure we're getting that data back. Sounds great. I don't see it on the screen. Why not? That's because we're not actually including it in our index page. So we'll just do content grid. Great. Okay. So at least we could see a few things here. That's great. Do we really need anything else to render this grid? Probably not, actually. This shows, like, the viewing data. We're we're definitely not gonna get to that. We got 12 minutes left. Let's just see if we can actually render something out. It shows something in a modal. All right. So we got our content grid. Great. Cool. Let's go in. I'm not sure if Tailwind has a great looking component for this. Gridless. Kind of. Yeah. Let's just create something on the fly. Right? So we'll go in. Alright. So that's the wrapper div. Then we'll do, we would probably fetch this via genre as well. Again, running short on time. Alright. We are going to do a, let's just keep this simple. Right? Add add a little bit of padding to the sides. We'll give this a grid. Grid calls, 5 across the screen, 6 across the screen. And then we'll do, let's do a, what, title card? And we will pass so we'll do some v four action here. V 4 title and data. The key is gonna be title dot ID. And then we have our, what, We're going to pass a title, title equals title. All right. So now we're going to create a new component. Call it title card dot view. And we will define some props. So props equals define props, Title object is prop type. Haven't set up our types here even though we're still using TypeScript. Alright, so we've got a card. I know Telen has like a card definition. Actually, the Nuxt UI library has a card as well. Let's just use it. So we got a card. There's a card. U card is the name of this. U card. And what do we have inside the card? We have, I don't know if these actually need to be h tags or not. We'll just use a p tag. Got the title dot name. If I can actually type this. Right? We've got an image for the card. Source equals title. No, no, no, no. We're gonna use Nuxt image because I have it preconfigured. And I'll just show you that really quickly. If I go to Nuxt Config, ba ba ba ba, where are you? Where are you, mister Image? Image. Okay. So Nuxt Image Directus is a provider here. We just give it a base URL that communicates with our Directus instance and allows us to do image transformations on the fly, which is really nice. So we got source title dot thumbnail. K. What do we do? Like, what? With full height 24 maybe? Sounds good. We got a title. Let's make this font bold. I'm assuming that's what it looks like. Oh, no. It's actually just a card. Right? Alright. Let's see what this looks like. We'll go back to our index page, content grid. Why don't we have our content? Why is content grid not rendering anything? Do we save it? What's going on here? Dev server. Why are we not rendering? Title card. Content grid. There is no data. What are we doing there? We just had our data. Right? Am I being silly? Pre data. Is that not showing? Did I break something? What is going on? Use, async data, content grid, titles, read items. Where did you go? Did I not just have this, like, a minute ago? Title. Title equals title. Hydration mismatch. If I look at content grid, like, we should have data. Right? Let me just look at the Nuxt fetching. Sometimes I even I use Nuxt all the time, and I'm not sometimes I have to go back to the oh, we gotta wait. Yeah. I don't know how that worked the first time. Right? Next image not showing. Okay. So we're missing some images there. Why are we missing images? So we are missing images because, let's do let's add our grid back. Grid calls 6bg grid. Do we need an actual BG? Okay. Maybe grid calls 4. That's fine. How are we doing on the time? We got 6 minutes left to actually render something up. Px8py12. Just pad this out a little bit. Alright. What's next? Our our images are not displaying. Right? How do we fix that? It is actually gonna be inside our Directus instance. So it is a permissions issue. Pretty sure if I were to take a look at our network requests, look at it like images here, you have 403 forbidden. So, I would go into not my docs. We'll open up the Directus instance. Go to 8055. And I somehow managed to log myself out as well. Alright. We go into access control. We've got our content here, but then we're gonna go in and allow access to the direct us files collection. So that way we can actually serve those images on the front end. Okay. Great. Maybe title card wasn't a great idea there by using the Nux card. Right? Let's just call it div. Oh, forgot to close that. Alright. So there's the diff. This is going to be Object, Cover, Trash Truck Christmas. Maybe we need to make that a little higher, 32. Alright. How we doing on time? 4 minutes. Can we get this to actually render? Alright. Button at click. Alright. Quick and dirty. Let's do this. We're going to add a composables. Actually, we could just pass this up as well. Alright. At click, we're going to define some omits. So omits equals define omits. We're gonna tell this select title. Select title? I think that's the syntax. Alright. Alright. Select title. Emit. Select title. And we're gonna pass the what? Prop no. It should just be title dot ID. Is that right? Or does that do I have to wrap that? No. Where are we getting here? Select title does not exist. Let's just call this function. On click, emit select title, props dot title, on click. Cool. Okay. And then inside our content grid, let's do this where we've got a modal. K. And we've got is open. That's false. Select title. And then what are we gonna do for the title? Let's do title, maybe content. Right? Content ref equals null. GitHub Copilot here for the win on this one. Right? Do we have a video component? I don't have a video component. So we're just gonna do, what, an iframe, class, aspect, style. Let's call it video. We'll just add a quick style here. Dot video aspect ratio 16 by 9. Alright. The well, the source is gonna be the content dot YouTube URL. Is that actually gonna work? I think I've got do I have some strings in here? Do I have a video content? I don't. Alright. Let's rely on AI here. Generate YouTube embed. Okay. So that returns the video ID. Let's see what AI comes back with. YouTube embed video ID. Okay. Great. Alright. So there's gonna be our source. We'll say generate YouTube video embed URL. Oh, nope. That's YouTube embed URL. Like, somewhere in the starter, I may already have this. Generate YouTube embed URL, and that is gonna be the content dot YouTube URL. Alright. So what am I missing? I'm gonna need to close that. Okay. And then on our select title, right, we need to fetch the content for the title. Alright. So that's gonna be what? This is gonna be an async function now. How are we doing on time? We have hit time. So we ran out of time to actually flesh this out and render this content in a model, but I just wanna try it. Let's just pursue it really quickly and see where we're at. Constant. We are going to we don't need to actually use async data for this. We're gonna fetch this on the client side. So let's do constant title equals wait. Use direct us, read items, title. Read item. Is it gonna be a title? We're gonna fetch through the we got the root level fields. Let's take a look at that data model again. Right? Local hosts. Alright. So we got titles, then we've got content? Alright. So root level, we'll go content dot that. Select title. Title has already been declared. Oh, okay. Where do I have that at? Oh, I've already given it the title. Content. Alright. So if I click that, is it actually making the request? Select title. Oh, and then for the title card at select title, we're going to select title. Alright. So I can see that's making the request. I get the data here. There's our content. Do we have the YouTube URL? That's great. For some reason, it's not throwing the modal window. Right? Why is the modal not opening? Cannot read properties, undefined of YouTube URL. Alright. So we probably wrap this in, like, a computer or something. Selected content equals computed value. Alright. And this is going to be if selected content and we'll have selected content. Is that why? Alright. So it's actually throwing the modal, but why isn't that working? It's because I'm not passing the right stuff. Right? So it should be dot content dot YouTube URL. Is that gonna work? Where are you? Where are you? Modal. Title card. Where's the content grid? Our selected content. Content dot value. Oh, duh. Gosh. Forgetting to set the content. Content dot value equals content. Let's try it now. No. Still new. Okay. So I hate to waste everybody's time. Content dot oh, fetched content. Let's just try this real quick before I give this up. Equals fetched content. Holy moly. And there it is. Right? We have a Netflix clone. So a little after time, we made it actually render something, which is a win. Kudos to the Netflix team. Right? The truth of the matter is that it is very difficult to build Netflix in an hour. So I hope you enjoyed this episode. Like, what would happen next for me? Basically, continue to flesh this out, get the UI working and and wired up correctly. You know, if I wanted to copy Netlify or Netflix directly, I would probably make sure that when I loaded a title that was getting appended to the the actual query string in the URL. You might set it up on a different route that renders a model. But next steps would just be basically continuing down the route of fleshing out the the UI side of it and, you know, adjusting the data model inside our back end as needed. So that's it for this episode of 100 apps, 100 hours. The outcome, maybe we give it one thumbs up. You know, I'm sure Cisco and Evert would probably not approve. So I'll catch you on the next one. 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,122,165,166,167],"cb4e067f-9507-4e18-ab9a-435565f9e653","8434838a-8e4f-489a-8da2-fbf10de5de6a","c997b25e-400c-4350-bba4-f63853d844f7","1109be0d-8ab5-479b-a052-8ad30d9ffb1c","dcb952e0-7d13-43ea-9b1c-f2ca63efd07d","0aae287d-3916-4d91-9310-25828998e562","8fed0eee-43f6-4767-8b77-79da7a059821","9271a4fc-addf-4400-9591-f2f2ec07bd79","30c48566-52cd-4728-a633-ca9675acc959","c067c012-5fe1-4d70-8946-8bfc8e1b22c0",{"title":169,"tile":170},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"title":172,"meta_description":8},"Building a Netflix clone with open source backend",{"id":174,"slug":175,"season":176,"vimeo_id":177,"description":178,"tile":179,"length":180,"resources":8,"people":8,"episode_number":155,"published":181,"title":182,"video_transcript_html":183,"video_transcript_text":184,"content":8,"seo":185,"status":137,"episode_people":186,"recommendations":188},"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",[187],"e9e66fa8-0650-4e37-ae8b-74755fdd5dca",[],{"reps":190},[191,247],{"name":192,"sdr":8,"link":193,"countries":194,"states":196},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[195],"United States",[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,245,246],"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":248,"link":249,"countries":250},"Michelle Riber","https://meetings.hubspot.com/mriber",[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,437,438,228,439,440],"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",1773850448848]