[{"data":1,"prerenderedAt":439},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-swag-platform":121,"100-apps-100-hours-swag-platform-next":171,"sales-reps":187},{"items":4},[5,29,49,69],{"id":6,"title":7,"url":8,"page":8,"children":9},"522e608a-77b0-4333-820d-d4f44be2ade1","Solutions",null,[10,15,20,25],{"id":11,"title":12,"url":8,"page":13},"fcafe85a-a798-4710-9e7a-776fe413aae5","Headless CMS",{"permalink":14},"/solutions/headless-cms",{"id":16,"title":17,"url":8,"page":18},"79972923-93cf-4777-9e32-5c9b0315fc10","Backend-as-a-Service",{"permalink":19},"/solutions/backend-as-a-service",{"id":21,"title":22,"url":8,"page":23},"0fa8d0c1-7b64-4f6f-939d-d7fdb99fc407","Product Information",{"permalink":24},"/solutions/product-information-management",{"id":26,"title":27,"url":28,"page":8},"63946d54-6052-4780-8ff4-91f5a9931dcc","100+ Things to Build","https://directus.io/blog/100-tools-apps-and-platforms-you-can-build-with-directus",{"id":30,"title":31,"url":8,"page":8,"children":32},"8ab4f9b1-f3e2-44d6-919b-011d91fe072f","Resources",[33,37,41,45],{"id":34,"title":35,"url":36,"page":8},"f951fb84-8777-4b84-9e91-996fe9d25483","Documentation","https://docs.directus.io",{"id":38,"title":39,"url":40,"page":8},"366febc7-a538-4c08-a326-e6204957f1e3","Guides","https://docs.directus.io/guides/",{"id":42,"title":43,"url":44,"page":8},"aeb9128e-1c5f-417f-863c-2449416433cd","Community","https://directus.chat",{"id":46,"title":47,"url":48,"page":8},"da1c2ed8-0a77-49b0-a903-49c56cb07de5","Release Notes","https://github.com/directus/directus/releases",{"id":50,"title":51,"url":8,"page":8,"children":52},"d61fae8c-7502-494a-822f-19ecff3d0256","Support",[53,57,61,65],{"id":54,"title":55,"url":56,"page":8},"8c43c781-7ebd-475f-a931-747e293c0a88","Issue Tracker","https://github.com/directus/directus/issues",{"id":58,"title":59,"url":60,"page":8},"d77bb78e-cf7b-4e01-932a-514414ba49d3","Feature Requests","https://github.com/directus/directus/discussions?discussions_q=is:open+sort:top",{"id":62,"title":63,"url":64,"page":8},"4346be2b-2c53-476e-b53b-becacec626a6","Community Chat","https://discord.com/channels/725371605378924594/741317677397704757",{"id":66,"title":67,"url":68,"page":8},"26c115d2-49f7-4edc-935e-d37d427fb89d","Cloud Dashboard","https://directus.cloud",{"id":70,"title":71,"url":8,"page":8,"children":72},"49141403-4f20-44ac-8453-25ace1265812","Organization",[73,78,84,88],{"id":74,"title":75,"url":76,"page":77},"1f36ea92-8a5e-47c8-914c-9822a8b9538a","About","/about",{"permalink":76},{"id":79,"title":80,"url":81,"page":82},"b84bf525-5471-4b14-a93c-225f6c386005","Careers","#",{"permalink":83},"/careers",{"id":85,"title":86,"url":87,"page":8},"86aabc3a-433d-434b-9efa-ad1d34be0a34","Brand Assets","https://drive.google.com/drive/folders/1lBOTba4RaA5ikqOn8Ewo4RYzD0XcymG9?usp=sharing",{"id":89,"title":90,"url":8,"page":91},"8d2fa1e3-198e-4405-81e1-2ceb858bc237","Contact",{"permalink":92},"/contact",{"items":94},[95,101,107,113],{"id":96,"title":97,"url":8,"page":98,"children":100},"8a1b7bfa-429d-4ffc-a650-2a5fdcf356da","Cloud Policies",{"permalink":99},"/cloud-policies",[],{"id":102,"title":103,"url":81,"page":104,"children":106},"bea848ef-828f-4306-8017-6b00ec5d4a0c","License",{"permalink":105},"/bsl",[],{"id":108,"title":109,"url":81,"page":110,"children":112},"4e914f47-4bee-42b7-b445-3119ee4196ef","Terms",{"permalink":111},"/terms",[],{"id":114,"title":115,"url":81,"page":116,"children":118},"ea69eda6-d317-4981-8421-fcabb1826bfd","Privacy",{"permalink":117},"/privacy",[],{"description":120},"\u003Cp>A composable backend to build your Headless CMS, BaaS, and more.&nbsp;\u003C/p>",{"id":122,"slug":123,"vimeo_id":124,"description":125,"tile":126,"length":127,"resources":8,"people":128,"episode_number":132,"published":133,"title":134,"video_transcript_html":135,"video_transcript_text":136,"content":8,"status":137,"episode_people":138,"recommendations":152,"season":153,"seo":8},"c997b25e-400c-4350-bba4-f63853d844f7","swag-platform","894282937","Gifting platforms are how we get goodies in the hands of community members and customers. Bryant needs to figure out how to manage products, build pages to handle the giveaways, and manage fulfillment.","9c659211-390a-4bfa-afd4-21fd08c80fbe",67,[129],{"name":130,"url":131},"Bryant Gillespie","https://directus.io/team/bryant-gillespie",3,"2023-12-22","Mission: Swag Platform","\u003Cp>Speaker 0: Alright. Alright. Alright. Welcome back to the next episode of 100 Apps 100 Hours where we rebuild or build some of your favorite apps in 1 hour or less or die trying or publicly fail trying is probably more appropriate. I'm your host, Brian Gillespie, developer advocate at Directus.\u003C/p>\u003Cp>Welcome. Super excited to have you. Today, we are going to be building a gifting platform. Oh, Brian, that sounds really exciting. Actually, to me, it is.\u003C/p>\u003Cp>So let's dive into the details of this. I've recently gone through at Directus and set up a swag program. So we've got tons of users worldwide that use our software getting, merchandise like branded cups or mugs or t shirts, into the hands of our user base, incredibly difficult challenge when you consider that we've got users in the US, we've got users in Germany, France, Namibia, just every country that you can think of. We've got people that we would like to thank and show appreciation for. Very challenging.\u003C/p>\u003Cp>So inter gifting platforms. We are going through the process currently with a company called swag.com. They offer a shopping cart and e commerce experience where you can log in and, basically pick your swag, upload your designs, and place orders that they will then fulfill for you. There's quite a few of these types of companies like Sendoso, I found another one called SwagUp, and this one is Codus Design. It basically a swag platform where you place an order, they manage all the fulfillment and inventory and actually delivering those items to your clients or your customers or your potential leads, potential prospects.\u003C/p>\u003Cp>They handle all of that for you. The part that I was very interested in, and I'm not discounting the fulfillment and warehousing and all the other bits and bobs that are included in here. That's probably the really difficult stuff. But, our past ecommerce or past swag solution was a Shopify store. And when you're trying to send somebody a thank you gift, it feels a little weird to have them check out on a Shopify store and enter a coupon code to actually get that get that swag or get that gift that you're trying to send them.\u003C/p>\u003Cp>And one of the reasons why I really like these platforms is the ability to send out a thank you page that looks very similar to this. There's there's no checkout. They don't have to enter a credit card or a coupon code. I send them a unique link. They can pick their swag, and we get them to enter in their information like their first name, email, country, and address so we know where to ship it.\u003C/p>\u003Cp>It is a great experience for them, great experience for us, win all around. That's what we're gonna be building today. Let's dive in. Just a quick recap of the rules. There's 60 minutes to plan and build, so that's plan and build in 60 minutes no more no less, And aside from that there are new rules.\u003C/p>\u003Cp>We'll use whatever we have at our disposal whether that's AI or all sorts of other cheat codes from around the Internet. Let's get started. We're gonna dive in. Alright, so before I build any apps, I usually like to take at least 5 minutes to plan. These planning sessions are always fun because it's hard to test your assumptions to get feedback when you have 5 minutes to plan an app.\u003C/p>\u003Cp>But here is my initial functionality that we're gonna build into this app. We're gonna need to manage our products and product options. We will need to, create a giveaway page or several different things that the the companies that I looked at called this, like a redemption page. I think giveaway is probably a good one. Thank you page, create a giveaway, a redemption page.\u003C/p>\u003Cp>Place an order, place a request. Not sure it's a order because it's free, but hey, that's what we'll do. User places a request, k, let's say user can place an order request, and then we manage and fulfill those through the app. Alright. So that's the app that we're gonna build.\u003C/p>\u003Cp>There are 2 components. There's a back end. There's a single page on the front end. What are we gonna need as far as our data model? What is that gonna look like?\u003C/p>\u003Cp>So we'll jump in and let's do, we certainly have products. Oh, I'm not ready to drag arrows yet. We've got our products. What is next? We've got our giveaway page, right?\u003C/p>\u003Cp>Giveaway. But then we have customers, you know, we'd probably wanna track that as a separate table or those could just be people. And then we'll have orders and probably some actual order items. So when we look at this, all our products roll up to a giveaway and we this is probably gonna be like a mini to mini relationship because we could have many different products on many different giveaways. When we go to products those are gonna be to the order items, customer orders or items will be orders attached to giveaways.\u003C/p>\u003Cp>I I just really like drawing arrows half the time. So as far as the rough data model, this looks pretty good. You know, curious to hear your feedback if if you set up ecommerce before, especially on how you handle like product variants and things like that. But let's dive in and not waste too much more time. Let's actually start building this.\u003C/p>\u003Cp>I'm just gonna move this off to the side. What does our setup look like as far as how we're gonna build? I've got a local Nuxt starter app set up. I've got a Directus folder where I've got a Docker Compose file that just simply creates my Directus instance that we're gonna be using on the back end. And then this Nuxt application just has a SDK for Directus already pre configured with a plugin using the the Nuxt plugins and, you know, nothing too fancy.\u003C/p>\u003Cp>The only, like, major difference here is there's a composable, view composable included for making the Directus requests, because Nuxt has that nice auto import feature. Alright, so let's log into our Directus instance and start building something right away. Let me look at what I'd use for my password here, just paste that and as we look let me get my data model up in view so we can actually start building. But as we look, this is a blank instance, I'm not doing any kind of crazy cheating here. Let's start building.\u003C/p>\u003Cp>So we'll create a new collection for products, we'll track some dates on this, you know, is this a draft product or not? Maybe we don't really need that for this particular instance. Alright. So on our products, what do we have? What are we using?\u003C/p>\u003Cp>We've got a name for our products, that's great, that's just a string. We've got a description for our product, We probably have some images for our product. So we've got some images. This is going to be using the relational fields inside Directus. And what's happening behind the scenes here as I'm building these collections and adding fields to it, the Directus API is mirroring this setup inside my Postgres database.\u003C/p>\u003Cp>And at the end of this, as soon as I get done building this data model, I'll have a ready to go API that I can then call on my front end to render those products. So we've got name, description, images, we're probably gonna have some options for our products like color and size. A more robust setup here would be to create separate collections for those, you know, a a product options and then maybe a product options values so that that could be dynamic. To streamline this, I think I wanna go with the repeater inside Directus. So this is just a JSON store and we'll call this options.\u003C/p>\u003Cp>So our options, we're gonna edit the fields here. The first option is gonna be the name of the option and then that'll be a string. We require a value for that, that'll be an input. It's fine. Let's make it full width and then next let's add values.\u003C/p>\u003Cp>So we're gonna get a little meta here and then I am going to embed a repeater within a repeater. Sounds pretty wild. So the field here, let's call it value. And I could probably have like a label and value in case the the value that I wanted to track was different than the label, but this looks pretty okay. We'll hit Save.\u003C/p>\u003Cp>We've got name, we've got description, we've got images, you know, if I had something like a SKU we can work on that, but let's keep it simple, right, let's keep moving. What's next? We have our giveaway. So this is gonna be a giveaway page, and if I pull up one, like, the direct to sample, we've basically got a logo, we've got a headline, we've got some description, then we have the products and we have a form. So let's add a table for giveaways.\u003C/p>\u003Cp>We'll go in and we'll set a status on the giveaway if this is published or not. Let's go in, we'll create a new field, we'll call it headline. We'll just make that a string. Then we have a description that we could set. So we'll do a description.\u003C/p>\u003Cp>Again that will be rich text so we could potentially have, lists or, you know, bold italicized text. And then we want to add our products. Right? So with our products, we're gonna use the relationship features inside Directus, and we're going to use that many to many relationship. Because one giveaway could have many products, and we could use many products on many giveaways on we use one product on many giveaways.\u003C/p>\u003Cp>So the let's call this field products. Our related collection is also gonna be products, and we don't want to allow duplicates. We do wanna show a link to the item so we can go back to that product. And I'm just gonna open this in advanced mode. So behind the scenes, what's happening when we use this many to many relationship?\u003C/p>\u003Cp>Inside the Directus or inside our Postgres database, Directus is creating a junction table called giveaways_products. Now I could adjust what that table is called, and even the foreign keys that it's setting up inside that junction collection, what it's naming those. But I'm just gonna go with autofill in this case. If I want to add the reverse relationship back on the products, so if I wanna reference those giveaways on the products themselves, I can do that here as well. And then maybe we add a sort field just to control the order.\u003C/p>\u003Cp>Maybe I want the shirt to show up first before the other items. Great. Directus is gonna tell me what all it's going to create inside my database, and now we've got our products. What else do we need? Do we need anything else here?\u003C/p>\u003Cp>The form, maybe we hard code that. Great. So if we look what else? We've got a customer, we've got orders, and order items. Alright.\u003C/p>\u003Cp>So let's go ahead and set those up as well. So we got a customer, actually let's just call it persons, people. Naming stuff is always the hardest thing. This may not be a customer. Right?\u003C/p>\u003Cp>So let's just go with the more semantic name and call it people. So people have a first name, they have a last name, they have an email address, and are we what else are we capturing? Capturing an address for those people. I'm assuming that could be on the order itself. Let's just roll with this.\u003C/p>\u003Cp>Right? First name, last name. Maybe we'll capture the address on the order. We could store it here as well, but we'll keep it simple. So we've got then we have an order and the orders are going to what?\u003C/p>\u003Cp>What are we gonna have on the order? The order is going to have a customer or a person, where we who's the person attached to that order, that's gonna be a many to one relationship. Alright. So we got the people, person, people, are already regretting this decision. No turning back now.\u003C/p>\u003Cp>Right? And then on the reverse side of that, on our people, we'll show the orders. People orders within the data model. Great. Alright.\u003C/p>\u003Cp>So the person we have, an address, let's just store that as JSON data for now. So we'll do an address and what else? We have a status is approved, maybe. This is approved, so we want approval before we ship all of these out. Let's add that to the top.\u003C/p>\u003Cp>And then last but not least, we want to create the actual items involved with this specific order. So we've got an order items table or you could call it orders items. Again, we get into splitting hairs here. What else do we need? We probably do need a sort for those blah blah blah.\u003C/p>\u003Cp>Alright, so we got our order, we got our order items. The items are gonna have a link back to the product. So we've got a related collection is gonna be the products. So when we put those items in the cart, the order item will have a product, so that'll be a relationship and then let's just have a JSON field to store our product options. Maybe we call it item options just to keep them separated.\u003C/p>\u003Cp>Good. And then there is a 1 or many to 1 relationship. So there are many items to one order, back to that order. So we've got the order, the related collection is orders. Tell me I didn't.\u003C/p>\u003Cp>I did, didn't I? So this is a boo boo. I've got orders here. It should be or I've got order should be orders. That should be a a fun little thing to work around.\u003C/p>\u003Cp>Now, I could go into the database itself and edit that if I wanted to, but, no worries. We'll just roll with what we got. There's an order. It's a order. Sometimes you goof up.\u003C/p>\u003Cp>What do you do? You keep rolling. Alright. So we've got our data model. Right?\u003C/p>\u003Cp>Looks great. Let's go in and maybe add a giveaway. And we can just duplicate this one, right? Directus Swag. Still learning Arc.\u003C/p>\u003Cp>I like it as a browser. Yeah. If anybody out there is a Arc expert, maybe you could give me a couple lessons like a tutorial session or something. Alright, so we got our headline, we've got a description. Let's add a product, we don't have any products at this point, so let's create a product.\u003C/p>\u003Cp>We've got the Directus, let's let's call it the Bunny Tumbler 20 ounce, Great. This is the most amazing tumbler ever. Great. Now we want to add some images to this. So, I can actually import images from a URL inside Directus, which is super nice, especially for speed runs like this.\u003C/p>\u003Cp>I just click upload a file, click the little link that will import our file. Looks great. For our options, we've got color and for the value, we've only got one. It's just white. We're gonna offer that in white.\u003C/p>\u003Cp>Great. Okay. Let's go in and add one more product. We've got our Directus logo tee. Kinda sounded like goatee, didn't it?\u003C/p>\u003Cp>This is the softest t shirt you'll ever wear, and again, we'll just go in and let's copy the image address and you can see we've got a couple options here. We've got extra small through 2 x, so we'll add those in there as well. Just upload our file. Great. For our options, we've got color, which is a dark heather.\u003C/p>\u003Cp>I believe I should know, I'm the one who ordered this stuff. And then let's add another option for what size. Alright. So we got extra small, we got small, we got medium. It'd be really handy if we could just do this in line, wouldn't it?\u003C/p>\u003Cp>I'm sure there's a way. Where there's a will, there's a way. Alright. So we've got some extra small through 2 x. We've got our products.\u003C/p>\u003Cp>We've added them to a giveaway. Let's go ahead and publish this giveaway and hit save. Great. So we've got our giveaway, and let me just prove it to you. Let's open up Directus.\u003C/p>\u003Cp>Alright. So this is our actual database here. We can see we've got order. We've got some directus tables that directus has created for us, and then we have giveaways. So there's our data.\u003C/p>\u003Cp>Directus prefixes all the metadata tables that it so it keeps the SQL database pure. That way, you know, you're not locked in or anything like that if you decide to migrate away. Very nice. Very nice. So now let's actually take a look at our giveaway.\u003C/p>\u003Cp>Right? How do we fetch this on the front end? And Directus gives us these REST APIs out of the gate. So if we go to Directus URL, which is local host 805 5 items giveaways, we get no permissions. So I have to go back and by default, Directus controls locks down all of my data.\u003C/p>\u003Cp>It wants us to be secure. So we've got 2 initial roles in an account. We've got a public role, which controls what data is accessible without authentication. And then we have the administrator role, which has unrestricted access. Now if I was gonna put this behind a login, I would probably do something like creating a user role that didn't have access or didn't have admin access, but they would log in on our front end.\u003C/p>\u003Cp>In this case, I'm just going to use the public role. We are going to allow people to see the giveaways, we're gonna allow them to view the giveaways, they can create orders and order items, they'll need to be able to create people, and we could also have, like, a server token, where we're doing some of this on the the server side as well. But we're not super concerned with security on this one because we've only got 40 minutes left to build this app. So we will allow access to see the products, see all the product files, and then we're going to go into the Directus Files under the System Collections and we're gonna allow that. So now if I go back, I open this up go to 8055/items/ giveaways.\u003C/p>\u003Cp>Boom. There's our giveaway data. Looks great. And we don't see any of our products. I'm gonna show you one of my favorite features about Directus Now and that I'm using the REST APIs.\u003C/p>\u003Cp>It also generates GraphQL APIs for you, but this is great. This is one of the things that I love. I could go in and tell it specifically what fields I want using the REST API, and I can even fetch so I'll just do products dot wildcard there. I could even go in and fetch products underscore ID the related data in a single call. So now you can see I've got our giveaway page data up here.\u003C/p>\u003Cp>I've got our products data here with all of our options and things like that, and I've only had to make a single call. Amazing. But we're gonna run out of time if we don't start building an app. So we've got our Nuxt app running at local host 3,000. Let's open this up and start trying to build something.\u003C/p>\u003Cp>Cool. So we'll go in to our pages, Let's create a new folder. Giveaways. Okay. Looks like I already did that.\u003C/p>\u003Cp>Not sure why I cheated a little bit there. But, we will do giveaways slash ID. So we use brackets here to set up a dynamic route inside Nuxt. We'll just do a basic component here, and now I just want to fetch that giveaway data so we can render it. Now typically, if I was using server side rendering or something like that, I would probably want to use the Nuxt async data calls.\u003C/p>\u003Cp>It's got a nice use async data as the composable that has a lot of nice little stuff built in, but I'm not super concerned with that here and honestly, this is gonna be a private link. So what we're gonna do is just call the giveaway. We're gonna go giveaway equals await use directus, that's my composable that wraps the API or, I'm sorry, the SDK. I'm gonna do read item, that's the giveaway syntax or the, I'm sorry, the SDK syntax, the giveaway syntax. We don't even know what that is.\u003C/p>\u003Cp>And then I need to pass it an item. So before I do that I'm gonna use route from Nuxt and then I can do something like this where we have route dot params dot ID And then the 3rd argument here is just an object with options. So in this case, I'm just gonna use fields for right now and show you what that looks like. Now on the front end, typically what I'll do a lot is just render that out so I could see what we're working with. Now how do I actually navigate to this?\u003C/p>\u003Cp>Inside Directus, I could go into my data model for our giveaway page, and maybe I wanna set up a button where I can actually get to this specific item. So we'll just create a new button, we'll say view giveaway, And, you know, if this is live, obviously, you're gonna change the URL. But this is gonna be my Nuxt URL, HTTP / localhost3000/giveaways/id, and then I can click this plus button to add the dynamic variables from the ID of this item. If you added the raw value, you can see we're just using mustache syntax here. We'll hit save, save, you give away.\u003C/p>\u003Cp>Looks good. We go to the giveaway. Now I've got this button I could click that I view giveaway and boom, should be showing me that, but instead I'm getting forbidden. Why are we getting forbidden? If I look, I could see that I left off the s.\u003C/p>\u003Cp>I'm assuming that's probably part of it. So let's just refresh, and boom. Now I can see our giveaway. So here's our actual data for this specific giveaway. It's great.\u003C/p>\u003Cp>So let's style this bad boy a bit. Trying to remember what I've actually got in here as far as like the layouts. So the Nuxt layout directory is super handy, this is the default layout, looks good. What are let's just copy what we've got on our index page here, and we'll go to giveaways. Alright.\u003C/p>\u003Cp>So I don't even know that we wanna flex all of this, do we? Let's give it a purple background, so we'll do pgprimary500 with full height full screen. What do we got? Okay. There we go.\u003C/p>\u003Cp>Alright. And then we'll give it a bit of padding. Now we'll do like a white background. I think that's kind of similar to what we've got here. Yeah.\u003C/p>\u003Cp>We got some funky business going on there, but we got PG white. Yeah, p 8 and then we've got, okay. Thank you GitHub Copilot for the assist. We got the giveaway dot title, giveaway dot description, that's not quite gonna cut it. So we've got the div here, That's actually HTML that we're getting back, so let's do the pros class in tailwind.\u003C/p>\u003Cp>There we go. GitHub Copilot is getting a lot better these days. Is it still is it totally there? I don't think so. Okay.\u003C/p>\u003Cp>So we appreciate you big time. So as a thank you, please select your color and reset. Maybe this is text center, I wanna center that up. What do we have here? Bgymaxwith3xlmxauto.\u003C/p>\u003Cp>We'll get that in the middle of the page. Why is our h one not rendering? Right? This should be a big bold font. It's not rendering, and if I actually took the time to figure out my types, it would be because that's not the actual key.\u003C/p>\u003Cp>It is headline. Alright. So we've got our direct to swag headline. Let's center that up as well. Cool.\u003C/p>\u003Cp>Alright. Let's add in our products. So if we open up our Vue dev tools, we go to ID, and I just look at the data that this actual component has, you can see we still have to fetch those products. So what I could do is come into the field section, we'll go to products, We will within that, we're gonna fetch the product underscore ID, and we'll get all the root level fields for that. Let's see what that looks like.\u003C/p>\u003Cp>Products. Why are you not giving me what I want? Of course we can always do this as well. Give away. Products is not coming through.\u003C/p>\u003Cp>Do we have permissions enabled for those products? Let's take a look. Giveaway products, people products. Looks like we've got read permissions set up for all of that. What am I doing wrong?\u003C/p>\u003Cp>Products. Products. ID. Is that it? Where are you?\u003C/p>\u003Cp>Okay. There we go. In my haystack, fat fingered, forgot to put an s in here. My wife makes a likes to make fun of my sausage fingers anyway. So alright.\u003C/p>\u003Cp>So now we got our products. We'll render these products out. V for product and giveaway dot products. Just show the product. Alright.\u003C/p>\u003Cp>So there's the individual products. Again, part of the rules are there are no rules, so let's hop into Tailwind UI. Again, one of my favorite tools. Just a nice library of components that they've already got kind of set up for use cases such as this. Looks like we could make use of this particular one.\u003C/p>\u003Cp>Maybe we don't want it served in a modal window though, so we just grab let's grab everything inside the dialog panel. And I'm not sure how the outcome is gonna go here. This is probably gonna break some stuff. Okay. So you can see we've got some products here.\u003C/p>\u003Cp>Nothing is actually working correctly because we don't have things like selected size, let's actually just skip this out and we'll bounce into a component. So let's call this the product card, we've got a v comp ts, got my little snippets set up and we'll just copy and paste that in there. So that's our templates, what do they have for the script section of this? Looks like they have selected color, and they've got some headless view. Luckily my Nox starter already has those set up and then they have a product.\u003C/p>\u003Cp>So I'll just copy this stuff down. Do we get something on this end? We're getting somewhere. Looking good, except none of this is actually rendering out. That's okay though.\u003C/p>\u003Cp>We're gonna sort through that. Right? So we don't need the reviews. We'll just chop those. Let's get this down to bare bones.\u003C/p>\u003Cp>Right? The color picker, so we need the size picker. Add to bag, we're probably not gonna need that. View full details, we're not gonna need that, but this is not updating, so I need to actually go in and just swap this out. Product card, b 4 product card.\u003C/p>\u003Cp>And then we'll probably end up doing something like this where we have key equals product dot ID. We probably wanna fetch that ID here as well. So do that. Is that right? No.\u003C/p>\u003Cp>We're gonna put it here. ID. Alright. So now we're rendering a product card. How we doing on time?\u003C/p>\u003Cp>We got 27 minutes left. We are over halfway here. We still gotta be able to submit this form. And then we also are gonna want to pass it, so our product is gonna be product dot products underscore ID. Alright.\u003C/p>\u003Cp>So we're not doing anything here now because I'm just using the default setup from Tailwind, from our Tailwind UI components. Where's that size guide coming at from as well? Let's get rid of that. Alright. So we just want the basic details, right?\u003C/p>\u003Cp>We're going to define some props, so we'll go prod const props equals define props. Product type is an object. Again, normally I would type this out with an interface or something like that inside using typescript, so we've got that, but, yeah, whatever. Alright. That should break this particular item.\u003C/p>\u003Cp>If I refresh, I'm probably gonna get a whole bunch of stuff going on. Got a product. Oh, let me just clear this out because now we no longer have product there. Okay. So we got the bunny tumbler, we've got some colors, we've got some stuff going on.\u003C/p>\u003Cp>Is this open and closed? We don't even need that. Alright. So this particular NUC starter and I think I'm gonna have to make this available online just in case anybody wants to look it up and decide if I was using steroids or not, but we'll do product dot images dot 0. Let's see where where that gets us.\u003C/p>\u003Cp>Not very far. Alright. So a Nuxt Image is already set up on this particular product or I'm sorry, this particular starter. Let me reload the page here and see what's happening. Alright.\u003C/p>\u003Cp>So we've got our products. We're passing that product down to the product card. And what are we actually getting for the images? We're just getting, not getting anything for the images. So, I could go back up to our giveaway.\u003C/p>\u003Cp>And now let's drill into our images, if I could stop making typos. Images. Boom. Let's see what we got now. Images and array of objects.\u003C/p>\u003Cp>At least now we have a direct us files ID that we can use to access that image. I would probably, you know, if you wanted to get, like, the title description, the alt text, stuff like that, we could drill in even further like this where we have the directus_files_id, and we get the ID, we get the title, get the description. Now let's look at what we've got. We just keep getting further and further into nesting. But, hey, super helpful anyway.\u003C/p>\u003Cp>Alright. So now we're getting that if we go to our product card, maybe we just use like a computer prop here. Constant image equals computed and then we'll do return product dot no. We're gonna do props dot product dot images dotzero.directusfiles.id dotid. Alright.\u003C/p>\u003Cp>And then here inside our Nuxt image I can just do this where we say image. Clear that alt text for now. Actually we could use the title. Right? So we have let's change that we're returning the actual object instead of the ID We'll do image dot ID, and then the alt text could be image dot title or description.\u003C/p>\u003Cp>Great. Same result. We've got our colors. We're at what? 22 minutes, does that need to be overflow hidden?\u003C/p>\u003Cp>Where's that coming from? That coming from our I think this was set up for like a, like an application instead of a scrolling page. Bg primary and hide screen, hide full. Yeah. Okay.\u003C/p>\u003Cp>Let's not stress over that for the moment. We've got bigger fish to fry. So, we've got our images, we've got our name, let's get our product description into this, product name, product price, the product description is gonna be again vhtml. So we'll just use the Tailwind Class Pros vhtmlproduct.description. Close that.\u003C/p>\u003Cp>Let's see what we got. This is the most amazing tumbler ever. There we go. Alright. So how do we get the size and and, like, our different options to display?\u003C/p>\u003Cp>Alright. If we look at our product card here, we've got our different options, for the tumbler there's just one option. So we are going to actually, do we need I don't think we need both of these. Right? We will just loop through and create each of those based on the individual options.\u003C/p>\u003Cp>So we've got a radio group, and this is the color. Let's just call it the options picker, and we get a v four product, let's call it option. Options. Yeah. That's fine.\u003C/p>\u003Cp>And product dot options, then the key is gonna be options.name. Okay. And then here we'll say options dot name, color, selected color, This will probably be something like selected options and that'll be a ref and object. We can do it either way. Let's see what we got.\u003C/p>\u003Cp>V model selected. Options and then we'll use the option dot name. We use the key for that. Choose a color, we don't really need that. In product dot colors, in v for value in no, let's do option in options dot options, maybe we call it option, option dot name, Option dot name, selected option, option dot options, Option choice.\u003C/p>\u003Cp>Option value. Naming surface fun. Right? Option value dot value. Okay.\u003C/p>\u003Cp>And then the value is what? Optionvalue.value.optionvalue.value. With the color dot selected color Not sure what that's doing there. Let's reload and see what's broken. So at least we're looping through these.\u003C/p>\u003Cp>Color dot name. Option dot name. Color.bgcolor. Why are these not actually displaying? Radio group, what are we passing into you?\u003C/p>\u003Cp>We are running out of time. V4 and product options. So we got a radio group. Let's just omit that. Missing something somewhere.\u003C/p>\u003Cp>Product card, let's look at we got our options, product dot options dot name option in options. So the option dot name and option dot values is what we need to loop over. Boom, boom, boom, And looks like I copied the wrong one. So we would probably be showing the values here if I go back to, let's copy this actual radio group option here. What's the difference?\u003C/p>\u003Cp>Div class. Just copy and paste this inside their size and stock. Don't need it. This is gonna be the option value dot value. This is gonna give us what we want.\u003C/p>\u003Cp>Oh, hey. Look at this. Now we're getting somewhere. Right? Looking beautiful.\u003C/p>\u003Cp>Not exactly beautiful, but definitely better than what we had. Right? So now let's take a look at our product card. If we look at our product, selected options dot ref, why is our ref not showing for those? Let me just refresh.\u003C/p>\u003Cp>The props, there's our setup, the selected options, this is for the first one, the color is white. Okay. Now if I select the other ones, we check the product card for that one. Where are you? Mister product card.\u003C/p>\u003Cp>Selected options are color, dark heather. Looks like we could probably work with this. Right? Now, yeah, we can get carried away here and do like a composable or something like that for our form. Let's just do like a watch and if we were gonna watch what selected options selected options dot, What is the actual watch syntax?\u003C/p>\u003Cp>Deep true. No. What is the view watch syntax? Alright. With the composition API, what are we looking for?\u003C/p>\u003Cp>We've got a function. So we're gonna watch selected options. Okay. And then we've got the functions. So we got the value, console log, maybe we want to omit that, so we'll pass that up, const emits equals define emit And let's just omit, updated.\u003C/p>\u003Cp>Sounds great. Alright. Selected options, we are going to omit. Updated. We will value true.\u003C/p>\u003Cp>Okay. Alright. So if we go back to our ID here, let's add a handler for this. Maybe we just do our order here that will be reactive. Okay, and then we've got our order products, we've got the order, what do we have?\u003C/p>\u003Cp>A customer? No. It was the person, is what we called it. Alright. That person has a first name, last name, email, and then we have an address.\u003C/p>\u003Cp>Great. Alright. So now we've got an order. Cost order equals react. What's the problem with this?\u003C/p>\u003Cp>Alright. Great. So now we'll probably need a handler. So let's do a function to handle that emit. So we'll do update cart or update products, products order dot products okay and then when update update products.\u003C/p>\u003Cp>Cool. Alright. So let's verify and see what's happening now. So I'll just refresh my page. Unable to fetch.\u003C/p>\u003Cp>Oh, what am I doing wrong? Hydration completed but contains a mismatch. What what are we doing wrong here? This is always fun. V if or if we just wrap this really quickly.\u003C/p>\u003Cp>Giveaway. Failed to fetch dynamically imported module. I understand. Active from you. So what's going on?\u003C/p>\u003Cp>Dying on the vine here. I probably should have been using the, Nuxt Async data all along, We could probably get around this maybe just by using turning SSR off for now, but obviously, not the best solution. Does not have provided a named export called select. What is this? Is this in the product card?\u003C/p>\u003Cp>Oh, I don't know why we imported that. How did that get imported? Okay. So it probably wasn't, any issue with the SSR there but, oh well. Alright.\u003C/p>\u003Cp>So we're back on our giveaway dot ID. Let's refresh just to make sure we've got everything. Where is our where's our app at? Okay. So we look for our ID.\u003C/p>\u003Cp>Now we've got our order object and hopefully, this should be updating, but it's not. Emit updated. That could be why. I actually have to spell it correctly. So we refresh, try again.\u003C/p>\u003Cp>If we look at our products, are they updating? They're still not updating. Wonder why that is. So console dot log, good old console dot log products section. Okay.\u003C/p>\u003Cp>Products. Okay. Okay. So what do we actually want to do here? This will be we actually need to pass the product ID, don't we?\u003C/p>\u003Cp>So updated. Let's do this where we have an event. We want to pass the product ID and the event. Alright. Update products.\u003C/p>\u003Cp>This will be what? Clock is ticking. Let's cheat a little bit. Update the porter products array with the selected product. Let's see what GitHub Copilot comes up with really quickly.\u003C/p>\u003Cp>It will look through the array, find the index for the item. Okay. Nobody said it was, was easy. Right? So now we've got our products, we've got our ID, we've got the options for the product.\u003C/p>\u003Cp>What do we want to, It's actually the product. Right? What is that gonna be inside the database? Let's just take a look at it inside our data model. We got the order, we got a person, Did we not add that reverse?\u003C/p>\u003Cp>We didn't add that reverse, so we we're gonna need to add that really quickly. Many to 1, 1 to many, boom boom. Products. Oh, actually, that's gonna be our items. Alright?\u003C/p>\u003Cp>It's gonna be the items on the order. Order items, order. Okay. So that's actually gonna change. That'll be our items.\u003C/p>\u003Cp>And then our order item itself has the item options, so that'll be item options. Item options equals event, and then the product. Order products dot index item options. We'll just see if this goes according to plan. Okay.\u003C/p>\u003Cp>Blah blah blah. We take a look, we've got our giveaway page. What am I missing? Order dot, oh, order dot products dot push. Forgot to update this.\u003C/p>\u003Cp>So this will actually be order dot items, order dot items. Okay. So now we've got our items for the order. There's our product, 123. Shouldn't have 3 items, should it?\u003C/p>\u003Cp>Maybe that was a mistake. Running into snags everywhere. Lots of fun. Alright, so how do we actually submit this? We still don't have our form either, do we?\u003C/p>\u003Cp>Let's build a form. So a form. And actually, I think there is the Nuxt UI component that is built into this thing. So let's just do a form. What are those?\u003C/p>\u003Cp>A u input, u form group I think is the right one. Label equals first name, V model equals order dot first name. Oh. That's gonna be you and put the model order first name. Let's see what we get.\u003C/p>\u003Cp>Okay, now we've got a form, Let's actually wrap that just a little bit. Max width 3 x l. Actually, let's just move it up inside that. Why why are we doing that? There we go.\u003C/p>\u003Cp>Alright, so we've now we've got our form inside here. Bgwhite, shadow. Okay, we got our first name. What do we have? We got a last name, got an email, street, city, GitHub Copilot for the win on this one, And zip.\u003C/p>\u003Cp>Alright. That's not looking very pretty, so let's actually set these on a grid. Grid calls 2. Alright. This will be class grid callspan 2.\u003C/p>\u003Cp>And give it some gap, gap 2, let's say 6, right? Too much, gap 4. We'll give the form itself some space in between and then we just need a button, submit. Let's make it block. Okay.\u003C/p>\u003Cp>Class equals call spin 2. Alright. So big giant form, now we need to submit the order, submit order, await, direct us, use orders, order, giveaway dot ID, create item, that looks correct. Let's wrap this in a try catch error console dot error. Alright.\u003C/p>\u003Cp>Time is a ticking. Let's see, we got 43 seconds. Let's see if we can actually get this done. Brian Gillespie, email bryantat directus.io. 123 Main Street, Bluefield, West Virginia.\u003C/p>\u003Cp>Submit. Oh, duh. Dummy. Come on now. Submit dot prevent.\u003C/p>\u003Cp>Oh, no. He's typo. Submit dot prevent. Submit order. One second.\u003C/p>\u003Cp>We hit the timer here right at the very end, just as we were getting really good. I just wanna submit this out and see if it's actually gonna work. Test city, 1232222. Submit. Did we get an error?\u003C/p>\u003Cp>Post local items orders equals forbidden. So I'm getting that error because there are permission settings that we didn't control order items. We forgot to set that up. But now if we were to go back and just try this again, orders forbidden. Yeah.\u003C/p>\u003Cp>So, something in the way that I'm passing the data here, don't have permissions to access this. It is probably something to do with the person data that I did not get updated. And it's actually what? State Street Zip. This is the this is the main issue why you shouldn't trust AI.\u003C/p>\u003Cp>In our hurry, this actually should have been order dot address dot street dot city dot state, all of that. Didn't even pick it up. Hopefully, you're not coding against the clock like I am, but that's one of the the issues there. Test, test, test, test, submit. Still getting some issues.\u003C/p>\u003Cp>So, we tried our best. I still think this looks pretty good. And with another 20 minutes or so, 10 minutes or so, we could have completed this out where it would actually submit the order to Directus. But I hope this is a good example of just how quickly you can build apps with Directus, and a a front end framework like Nuxt. Directus gives you a lot out of the box, which makes speed runs like this possible.\u003C/p>\u003Cp>This was a lot of fun. I don't think that I'm gonna be replacing swag.com anytime soon, but, nevertheless, if you had different needs or you wanted your own customized platform, you could quickly and easily build it with Directus and have something that was specific to your company. These platforms like swag.com and others are obviously, they have a lot going on on the fulfillment side. But with Directus you could build custom ecommerce like this, custom experiences for people rapidly. So that is it for this episode of 100 apps 100 hours.\u003C/p>\u003Cp>Thanks for sticking it out with me and I hope you'll tune in for the next episode. Ciao.\u003C/p>","Alright. Alright. Alright. Welcome back to the next episode of 100 Apps 100 Hours where we rebuild or build some of your favorite apps in 1 hour or less or die trying or publicly fail trying is probably more appropriate. I'm your host, Brian Gillespie, developer advocate at Directus. Welcome. Super excited to have you. Today, we are going to be building a gifting platform. Oh, Brian, that sounds really exciting. Actually, to me, it is. So let's dive into the details of this. I've recently gone through at Directus and set up a swag program. So we've got tons of users worldwide that use our software getting, merchandise like branded cups or mugs or t shirts, into the hands of our user base, incredibly difficult challenge when you consider that we've got users in the US, we've got users in Germany, France, Namibia, just every country that you can think of. We've got people that we would like to thank and show appreciation for. Very challenging. So inter gifting platforms. We are going through the process currently with a company called swag.com. They offer a shopping cart and e commerce experience where you can log in and, basically pick your swag, upload your designs, and place orders that they will then fulfill for you. There's quite a few of these types of companies like Sendoso, I found another one called SwagUp, and this one is Codus Design. It basically a swag platform where you place an order, they manage all the fulfillment and inventory and actually delivering those items to your clients or your customers or your potential leads, potential prospects. They handle all of that for you. The part that I was very interested in, and I'm not discounting the fulfillment and warehousing and all the other bits and bobs that are included in here. That's probably the really difficult stuff. But, our past ecommerce or past swag solution was a Shopify store. And when you're trying to send somebody a thank you gift, it feels a little weird to have them check out on a Shopify store and enter a coupon code to actually get that get that swag or get that gift that you're trying to send them. And one of the reasons why I really like these platforms is the ability to send out a thank you page that looks very similar to this. There's there's no checkout. They don't have to enter a credit card or a coupon code. I send them a unique link. They can pick their swag, and we get them to enter in their information like their first name, email, country, and address so we know where to ship it. It is a great experience for them, great experience for us, win all around. That's what we're gonna be building today. Let's dive in. Just a quick recap of the rules. There's 60 minutes to plan and build, so that's plan and build in 60 minutes no more no less, And aside from that there are new rules. We'll use whatever we have at our disposal whether that's AI or all sorts of other cheat codes from around the Internet. Let's get started. We're gonna dive in. Alright, so before I build any apps, I usually like to take at least 5 minutes to plan. These planning sessions are always fun because it's hard to test your assumptions to get feedback when you have 5 minutes to plan an app. But here is my initial functionality that we're gonna build into this app. We're gonna need to manage our products and product options. We will need to, create a giveaway page or several different things that the the companies that I looked at called this, like a redemption page. I think giveaway is probably a good one. Thank you page, create a giveaway, a redemption page. Place an order, place a request. Not sure it's a order because it's free, but hey, that's what we'll do. User places a request, k, let's say user can place an order request, and then we manage and fulfill those through the app. Alright. So that's the app that we're gonna build. There are 2 components. There's a back end. There's a single page on the front end. What are we gonna need as far as our data model? What is that gonna look like? So we'll jump in and let's do, we certainly have products. Oh, I'm not ready to drag arrows yet. We've got our products. What is next? We've got our giveaway page, right? Giveaway. But then we have customers, you know, we'd probably wanna track that as a separate table or those could just be people. And then we'll have orders and probably some actual order items. So when we look at this, all our products roll up to a giveaway and we this is probably gonna be like a mini to mini relationship because we could have many different products on many different giveaways. When we go to products those are gonna be to the order items, customer orders or items will be orders attached to giveaways. I I just really like drawing arrows half the time. So as far as the rough data model, this looks pretty good. You know, curious to hear your feedback if if you set up ecommerce before, especially on how you handle like product variants and things like that. But let's dive in and not waste too much more time. Let's actually start building this. I'm just gonna move this off to the side. What does our setup look like as far as how we're gonna build? I've got a local Nuxt starter app set up. I've got a Directus folder where I've got a Docker Compose file that just simply creates my Directus instance that we're gonna be using on the back end. And then this Nuxt application just has a SDK for Directus already pre configured with a plugin using the the Nuxt plugins and, you know, nothing too fancy. The only, like, major difference here is there's a composable, view composable included for making the Directus requests, because Nuxt has that nice auto import feature. Alright, so let's log into our Directus instance and start building something right away. Let me look at what I'd use for my password here, just paste that and as we look let me get my data model up in view so we can actually start building. But as we look, this is a blank instance, I'm not doing any kind of crazy cheating here. Let's start building. So we'll create a new collection for products, we'll track some dates on this, you know, is this a draft product or not? Maybe we don't really need that for this particular instance. Alright. So on our products, what do we have? What are we using? We've got a name for our products, that's great, that's just a string. We've got a description for our product, We probably have some images for our product. So we've got some images. This is going to be using the relational fields inside Directus. And what's happening behind the scenes here as I'm building these collections and adding fields to it, the Directus API is mirroring this setup inside my Postgres database. And at the end of this, as soon as I get done building this data model, I'll have a ready to go API that I can then call on my front end to render those products. So we've got name, description, images, we're probably gonna have some options for our products like color and size. A more robust setup here would be to create separate collections for those, you know, a a product options and then maybe a product options values so that that could be dynamic. To streamline this, I think I wanna go with the repeater inside Directus. So this is just a JSON store and we'll call this options. So our options, we're gonna edit the fields here. The first option is gonna be the name of the option and then that'll be a string. We require a value for that, that'll be an input. It's fine. Let's make it full width and then next let's add values. So we're gonna get a little meta here and then I am going to embed a repeater within a repeater. Sounds pretty wild. So the field here, let's call it value. And I could probably have like a label and value in case the the value that I wanted to track was different than the label, but this looks pretty okay. We'll hit Save. We've got name, we've got description, we've got images, you know, if I had something like a SKU we can work on that, but let's keep it simple, right, let's keep moving. What's next? We have our giveaway. So this is gonna be a giveaway page, and if I pull up one, like, the direct to sample, we've basically got a logo, we've got a headline, we've got some description, then we have the products and we have a form. So let's add a table for giveaways. We'll go in and we'll set a status on the giveaway if this is published or not. Let's go in, we'll create a new field, we'll call it headline. We'll just make that a string. Then we have a description that we could set. So we'll do a description. Again that will be rich text so we could potentially have, lists or, you know, bold italicized text. And then we want to add our products. Right? So with our products, we're gonna use the relationship features inside Directus, and we're going to use that many to many relationship. Because one giveaway could have many products, and we could use many products on many giveaways on we use one product on many giveaways. So the let's call this field products. Our related collection is also gonna be products, and we don't want to allow duplicates. We do wanna show a link to the item so we can go back to that product. And I'm just gonna open this in advanced mode. So behind the scenes, what's happening when we use this many to many relationship? Inside the Directus or inside our Postgres database, Directus is creating a junction table called giveaways_products. Now I could adjust what that table is called, and even the foreign keys that it's setting up inside that junction collection, what it's naming those. But I'm just gonna go with autofill in this case. If I want to add the reverse relationship back on the products, so if I wanna reference those giveaways on the products themselves, I can do that here as well. And then maybe we add a sort field just to control the order. Maybe I want the shirt to show up first before the other items. Great. Directus is gonna tell me what all it's going to create inside my database, and now we've got our products. What else do we need? Do we need anything else here? The form, maybe we hard code that. Great. So if we look what else? We've got a customer, we've got orders, and order items. Alright. So let's go ahead and set those up as well. So we got a customer, actually let's just call it persons, people. Naming stuff is always the hardest thing. This may not be a customer. Right? So let's just go with the more semantic name and call it people. So people have a first name, they have a last name, they have an email address, and are we what else are we capturing? Capturing an address for those people. I'm assuming that could be on the order itself. Let's just roll with this. Right? First name, last name. Maybe we'll capture the address on the order. We could store it here as well, but we'll keep it simple. So we've got then we have an order and the orders are going to what? What are we gonna have on the order? The order is going to have a customer or a person, where we who's the person attached to that order, that's gonna be a many to one relationship. Alright. So we got the people, person, people, are already regretting this decision. No turning back now. Right? And then on the reverse side of that, on our people, we'll show the orders. People orders within the data model. Great. Alright. So the person we have, an address, let's just store that as JSON data for now. So we'll do an address and what else? We have a status is approved, maybe. This is approved, so we want approval before we ship all of these out. Let's add that to the top. And then last but not least, we want to create the actual items involved with this specific order. So we've got an order items table or you could call it orders items. Again, we get into splitting hairs here. What else do we need? We probably do need a sort for those blah blah blah. Alright, so we got our order, we got our order items. The items are gonna have a link back to the product. So we've got a related collection is gonna be the products. So when we put those items in the cart, the order item will have a product, so that'll be a relationship and then let's just have a JSON field to store our product options. Maybe we call it item options just to keep them separated. Good. And then there is a 1 or many to 1 relationship. So there are many items to one order, back to that order. So we've got the order, the related collection is orders. Tell me I didn't. I did, didn't I? So this is a boo boo. I've got orders here. It should be or I've got order should be orders. That should be a a fun little thing to work around. Now, I could go into the database itself and edit that if I wanted to, but, no worries. We'll just roll with what we got. There's an order. It's a order. Sometimes you goof up. What do you do? You keep rolling. Alright. So we've got our data model. Right? Looks great. Let's go in and maybe add a giveaway. And we can just duplicate this one, right? Directus Swag. Still learning Arc. I like it as a browser. Yeah. If anybody out there is a Arc expert, maybe you could give me a couple lessons like a tutorial session or something. Alright, so we got our headline, we've got a description. Let's add a product, we don't have any products at this point, so let's create a product. We've got the Directus, let's let's call it the Bunny Tumbler 20 ounce, Great. This is the most amazing tumbler ever. Great. Now we want to add some images to this. So, I can actually import images from a URL inside Directus, which is super nice, especially for speed runs like this. I just click upload a file, click the little link that will import our file. Looks great. For our options, we've got color and for the value, we've only got one. It's just white. We're gonna offer that in white. Great. Okay. Let's go in and add one more product. We've got our Directus logo tee. Kinda sounded like goatee, didn't it? This is the softest t shirt you'll ever wear, and again, we'll just go in and let's copy the image address and you can see we've got a couple options here. We've got extra small through 2 x, so we'll add those in there as well. Just upload our file. Great. For our options, we've got color, which is a dark heather. I believe I should know, I'm the one who ordered this stuff. And then let's add another option for what size. Alright. So we got extra small, we got small, we got medium. It'd be really handy if we could just do this in line, wouldn't it? I'm sure there's a way. Where there's a will, there's a way. Alright. So we've got some extra small through 2 x. We've got our products. We've added them to a giveaway. Let's go ahead and publish this giveaway and hit save. Great. So we've got our giveaway, and let me just prove it to you. Let's open up Directus. Alright. So this is our actual database here. We can see we've got order. We've got some directus tables that directus has created for us, and then we have giveaways. So there's our data. Directus prefixes all the metadata tables that it so it keeps the SQL database pure. That way, you know, you're not locked in or anything like that if you decide to migrate away. Very nice. Very nice. So now let's actually take a look at our giveaway. Right? How do we fetch this on the front end? And Directus gives us these REST APIs out of the gate. So if we go to Directus URL, which is local host 805 5 items giveaways, we get no permissions. So I have to go back and by default, Directus controls locks down all of my data. It wants us to be secure. So we've got 2 initial roles in an account. We've got a public role, which controls what data is accessible without authentication. And then we have the administrator role, which has unrestricted access. Now if I was gonna put this behind a login, I would probably do something like creating a user role that didn't have access or didn't have admin access, but they would log in on our front end. In this case, I'm just going to use the public role. We are going to allow people to see the giveaways, we're gonna allow them to view the giveaways, they can create orders and order items, they'll need to be able to create people, and we could also have, like, a server token, where we're doing some of this on the the server side as well. But we're not super concerned with security on this one because we've only got 40 minutes left to build this app. So we will allow access to see the products, see all the product files, and then we're going to go into the Directus Files under the System Collections and we're gonna allow that. So now if I go back, I open this up go to 8055/items/ giveaways. Boom. There's our giveaway data. Looks great. And we don't see any of our products. I'm gonna show you one of my favorite features about Directus Now and that I'm using the REST APIs. It also generates GraphQL APIs for you, but this is great. This is one of the things that I love. I could go in and tell it specifically what fields I want using the REST API, and I can even fetch so I'll just do products dot wildcard there. I could even go in and fetch products underscore ID the related data in a single call. So now you can see I've got our giveaway page data up here. I've got our products data here with all of our options and things like that, and I've only had to make a single call. Amazing. But we're gonna run out of time if we don't start building an app. So we've got our Nuxt app running at local host 3,000. Let's open this up and start trying to build something. Cool. So we'll go in to our pages, Let's create a new folder. Giveaways. Okay. Looks like I already did that. Not sure why I cheated a little bit there. But, we will do giveaways slash ID. So we use brackets here to set up a dynamic route inside Nuxt. We'll just do a basic component here, and now I just want to fetch that giveaway data so we can render it. Now typically, if I was using server side rendering or something like that, I would probably want to use the Nuxt async data calls. It's got a nice use async data as the composable that has a lot of nice little stuff built in, but I'm not super concerned with that here and honestly, this is gonna be a private link. So what we're gonna do is just call the giveaway. We're gonna go giveaway equals await use directus, that's my composable that wraps the API or, I'm sorry, the SDK. I'm gonna do read item, that's the giveaway syntax or the, I'm sorry, the SDK syntax, the giveaway syntax. We don't even know what that is. And then I need to pass it an item. So before I do that I'm gonna use route from Nuxt and then I can do something like this where we have route dot params dot ID And then the 3rd argument here is just an object with options. So in this case, I'm just gonna use fields for right now and show you what that looks like. Now on the front end, typically what I'll do a lot is just render that out so I could see what we're working with. Now how do I actually navigate to this? Inside Directus, I could go into my data model for our giveaway page, and maybe I wanna set up a button where I can actually get to this specific item. So we'll just create a new button, we'll say view giveaway, And, you know, if this is live, obviously, you're gonna change the URL. But this is gonna be my Nuxt URL, HTTP / localhost3000/giveaways/id, and then I can click this plus button to add the dynamic variables from the ID of this item. If you added the raw value, you can see we're just using mustache syntax here. We'll hit save, save, you give away. Looks good. We go to the giveaway. Now I've got this button I could click that I view giveaway and boom, should be showing me that, but instead I'm getting forbidden. Why are we getting forbidden? If I look, I could see that I left off the s. I'm assuming that's probably part of it. So let's just refresh, and boom. Now I can see our giveaway. So here's our actual data for this specific giveaway. It's great. So let's style this bad boy a bit. Trying to remember what I've actually got in here as far as like the layouts. So the Nuxt layout directory is super handy, this is the default layout, looks good. What are let's just copy what we've got on our index page here, and we'll go to giveaways. Alright. So I don't even know that we wanna flex all of this, do we? Let's give it a purple background, so we'll do pgprimary500 with full height full screen. What do we got? Okay. There we go. Alright. And then we'll give it a bit of padding. Now we'll do like a white background. I think that's kind of similar to what we've got here. Yeah. We got some funky business going on there, but we got PG white. Yeah, p 8 and then we've got, okay. Thank you GitHub Copilot for the assist. We got the giveaway dot title, giveaway dot description, that's not quite gonna cut it. So we've got the div here, That's actually HTML that we're getting back, so let's do the pros class in tailwind. There we go. GitHub Copilot is getting a lot better these days. Is it still is it totally there? I don't think so. Okay. So we appreciate you big time. So as a thank you, please select your color and reset. Maybe this is text center, I wanna center that up. What do we have here? Bgymaxwith3xlmxauto. We'll get that in the middle of the page. Why is our h one not rendering? Right? This should be a big bold font. It's not rendering, and if I actually took the time to figure out my types, it would be because that's not the actual key. It is headline. Alright. So we've got our direct to swag headline. Let's center that up as well. Cool. Alright. Let's add in our products. So if we open up our Vue dev tools, we go to ID, and I just look at the data that this actual component has, you can see we still have to fetch those products. So what I could do is come into the field section, we'll go to products, We will within that, we're gonna fetch the product underscore ID, and we'll get all the root level fields for that. Let's see what that looks like. Products. Why are you not giving me what I want? Of course we can always do this as well. Give away. Products is not coming through. Do we have permissions enabled for those products? Let's take a look. Giveaway products, people products. Looks like we've got read permissions set up for all of that. What am I doing wrong? Products. Products. ID. Is that it? Where are you? Okay. There we go. In my haystack, fat fingered, forgot to put an s in here. My wife makes a likes to make fun of my sausage fingers anyway. So alright. So now we got our products. We'll render these products out. V for product and giveaway dot products. Just show the product. Alright. So there's the individual products. Again, part of the rules are there are no rules, so let's hop into Tailwind UI. Again, one of my favorite tools. Just a nice library of components that they've already got kind of set up for use cases such as this. Looks like we could make use of this particular one. Maybe we don't want it served in a modal window though, so we just grab let's grab everything inside the dialog panel. And I'm not sure how the outcome is gonna go here. This is probably gonna break some stuff. Okay. So you can see we've got some products here. Nothing is actually working correctly because we don't have things like selected size, let's actually just skip this out and we'll bounce into a component. So let's call this the product card, we've got a v comp ts, got my little snippets set up and we'll just copy and paste that in there. So that's our templates, what do they have for the script section of this? Looks like they have selected color, and they've got some headless view. Luckily my Nox starter already has those set up and then they have a product. So I'll just copy this stuff down. Do we get something on this end? We're getting somewhere. Looking good, except none of this is actually rendering out. That's okay though. We're gonna sort through that. Right? So we don't need the reviews. We'll just chop those. Let's get this down to bare bones. Right? The color picker, so we need the size picker. Add to bag, we're probably not gonna need that. View full details, we're not gonna need that, but this is not updating, so I need to actually go in and just swap this out. Product card, b 4 product card. And then we'll probably end up doing something like this where we have key equals product dot ID. We probably wanna fetch that ID here as well. So do that. Is that right? No. We're gonna put it here. ID. Alright. So now we're rendering a product card. How we doing on time? We got 27 minutes left. We are over halfway here. We still gotta be able to submit this form. And then we also are gonna want to pass it, so our product is gonna be product dot products underscore ID. Alright. So we're not doing anything here now because I'm just using the default setup from Tailwind, from our Tailwind UI components. Where's that size guide coming at from as well? Let's get rid of that. Alright. So we just want the basic details, right? We're going to define some props, so we'll go prod const props equals define props. Product type is an object. Again, normally I would type this out with an interface or something like that inside using typescript, so we've got that, but, yeah, whatever. Alright. That should break this particular item. If I refresh, I'm probably gonna get a whole bunch of stuff going on. Got a product. Oh, let me just clear this out because now we no longer have product there. Okay. So we got the bunny tumbler, we've got some colors, we've got some stuff going on. Is this open and closed? We don't even need that. Alright. So this particular NUC starter and I think I'm gonna have to make this available online just in case anybody wants to look it up and decide if I was using steroids or not, but we'll do product dot images dot 0. Let's see where where that gets us. Not very far. Alright. So a Nuxt Image is already set up on this particular product or I'm sorry, this particular starter. Let me reload the page here and see what's happening. Alright. So we've got our products. We're passing that product down to the product card. And what are we actually getting for the images? We're just getting, not getting anything for the images. So, I could go back up to our giveaway. And now let's drill into our images, if I could stop making typos. Images. Boom. Let's see what we got now. Images and array of objects. At least now we have a direct us files ID that we can use to access that image. I would probably, you know, if you wanted to get, like, the title description, the alt text, stuff like that, we could drill in even further like this where we have the directus_files_id, and we get the ID, we get the title, get the description. Now let's look at what we've got. We just keep getting further and further into nesting. But, hey, super helpful anyway. Alright. So now we're getting that if we go to our product card, maybe we just use like a computer prop here. Constant image equals computed and then we'll do return product dot no. We're gonna do props dot product dot images dotzero.directusfiles.id dotid. Alright. And then here inside our Nuxt image I can just do this where we say image. Clear that alt text for now. Actually we could use the title. Right? So we have let's change that we're returning the actual object instead of the ID We'll do image dot ID, and then the alt text could be image dot title or description. Great. Same result. We've got our colors. We're at what? 22 minutes, does that need to be overflow hidden? Where's that coming from? That coming from our I think this was set up for like a, like an application instead of a scrolling page. Bg primary and hide screen, hide full. Yeah. Okay. Let's not stress over that for the moment. We've got bigger fish to fry. So, we've got our images, we've got our name, let's get our product description into this, product name, product price, the product description is gonna be again vhtml. So we'll just use the Tailwind Class Pros vhtmlproduct.description. Close that. Let's see what we got. This is the most amazing tumbler ever. There we go. Alright. So how do we get the size and and, like, our different options to display? Alright. If we look at our product card here, we've got our different options, for the tumbler there's just one option. So we are going to actually, do we need I don't think we need both of these. Right? We will just loop through and create each of those based on the individual options. So we've got a radio group, and this is the color. Let's just call it the options picker, and we get a v four product, let's call it option. Options. Yeah. That's fine. And product dot options, then the key is gonna be options.name. Okay. And then here we'll say options dot name, color, selected color, This will probably be something like selected options and that'll be a ref and object. We can do it either way. Let's see what we got. V model selected. Options and then we'll use the option dot name. We use the key for that. Choose a color, we don't really need that. In product dot colors, in v for value in no, let's do option in options dot options, maybe we call it option, option dot name, Option dot name, selected option, option dot options, Option choice. Option value. Naming surface fun. Right? Option value dot value. Okay. And then the value is what? Optionvalue.value.optionvalue.value. With the color dot selected color Not sure what that's doing there. Let's reload and see what's broken. So at least we're looping through these. Color dot name. Option dot name. Color.bgcolor. Why are these not actually displaying? Radio group, what are we passing into you? We are running out of time. V4 and product options. So we got a radio group. Let's just omit that. Missing something somewhere. Product card, let's look at we got our options, product dot options dot name option in options. So the option dot name and option dot values is what we need to loop over. Boom, boom, boom, And looks like I copied the wrong one. So we would probably be showing the values here if I go back to, let's copy this actual radio group option here. What's the difference? Div class. Just copy and paste this inside their size and stock. Don't need it. This is gonna be the option value dot value. This is gonna give us what we want. Oh, hey. Look at this. Now we're getting somewhere. Right? Looking beautiful. Not exactly beautiful, but definitely better than what we had. Right? So now let's take a look at our product card. If we look at our product, selected options dot ref, why is our ref not showing for those? Let me just refresh. The props, there's our setup, the selected options, this is for the first one, the color is white. Okay. Now if I select the other ones, we check the product card for that one. Where are you? Mister product card. Selected options are color, dark heather. Looks like we could probably work with this. Right? Now, yeah, we can get carried away here and do like a composable or something like that for our form. Let's just do like a watch and if we were gonna watch what selected options selected options dot, What is the actual watch syntax? Deep true. No. What is the view watch syntax? Alright. With the composition API, what are we looking for? We've got a function. So we're gonna watch selected options. Okay. And then we've got the functions. So we got the value, console log, maybe we want to omit that, so we'll pass that up, const emits equals define emit And let's just omit, updated. Sounds great. Alright. Selected options, we are going to omit. Updated. We will value true. Okay. Alright. So if we go back to our ID here, let's add a handler for this. Maybe we just do our order here that will be reactive. Okay, and then we've got our order products, we've got the order, what do we have? A customer? No. It was the person, is what we called it. Alright. That person has a first name, last name, email, and then we have an address. Great. Alright. So now we've got an order. Cost order equals react. What's the problem with this? Alright. Great. So now we'll probably need a handler. So let's do a function to handle that emit. So we'll do update cart or update products, products order dot products okay and then when update update products. Cool. Alright. So let's verify and see what's happening now. So I'll just refresh my page. Unable to fetch. Oh, what am I doing wrong? Hydration completed but contains a mismatch. What what are we doing wrong here? This is always fun. V if or if we just wrap this really quickly. Giveaway. Failed to fetch dynamically imported module. I understand. Active from you. So what's going on? Dying on the vine here. I probably should have been using the, Nuxt Async data all along, We could probably get around this maybe just by using turning SSR off for now, but obviously, not the best solution. Does not have provided a named export called select. What is this? Is this in the product card? Oh, I don't know why we imported that. How did that get imported? Okay. So it probably wasn't, any issue with the SSR there but, oh well. Alright. So we're back on our giveaway dot ID. Let's refresh just to make sure we've got everything. Where is our where's our app at? Okay. So we look for our ID. Now we've got our order object and hopefully, this should be updating, but it's not. Emit updated. That could be why. I actually have to spell it correctly. So we refresh, try again. If we look at our products, are they updating? They're still not updating. Wonder why that is. So console dot log, good old console dot log products section. Okay. Products. Okay. Okay. So what do we actually want to do here? This will be we actually need to pass the product ID, don't we? So updated. Let's do this where we have an event. We want to pass the product ID and the event. Alright. Update products. This will be what? Clock is ticking. Let's cheat a little bit. Update the porter products array with the selected product. Let's see what GitHub Copilot comes up with really quickly. It will look through the array, find the index for the item. Okay. Nobody said it was, was easy. Right? So now we've got our products, we've got our ID, we've got the options for the product. What do we want to, It's actually the product. Right? What is that gonna be inside the database? Let's just take a look at it inside our data model. We got the order, we got a person, Did we not add that reverse? We didn't add that reverse, so we we're gonna need to add that really quickly. Many to 1, 1 to many, boom boom. Products. Oh, actually, that's gonna be our items. Alright? It's gonna be the items on the order. Order items, order. Okay. So that's actually gonna change. That'll be our items. And then our order item itself has the item options, so that'll be item options. Item options equals event, and then the product. Order products dot index item options. We'll just see if this goes according to plan. Okay. Blah blah blah. We take a look, we've got our giveaway page. What am I missing? Order dot, oh, order dot products dot push. Forgot to update this. So this will actually be order dot items, order dot items. Okay. So now we've got our items for the order. There's our product, 123. Shouldn't have 3 items, should it? Maybe that was a mistake. Running into snags everywhere. Lots of fun. Alright, so how do we actually submit this? We still don't have our form either, do we? Let's build a form. So a form. And actually, I think there is the Nuxt UI component that is built into this thing. So let's just do a form. What are those? A u input, u form group I think is the right one. Label equals first name, V model equals order dot first name. Oh. That's gonna be you and put the model order first name. Let's see what we get. Okay, now we've got a form, Let's actually wrap that just a little bit. Max width 3 x l. Actually, let's just move it up inside that. Why why are we doing that? There we go. Alright, so we've now we've got our form inside here. Bgwhite, shadow. Okay, we got our first name. What do we have? We got a last name, got an email, street, city, GitHub Copilot for the win on this one, And zip. Alright. That's not looking very pretty, so let's actually set these on a grid. Grid calls 2. Alright. This will be class grid callspan 2. And give it some gap, gap 2, let's say 6, right? Too much, gap 4. We'll give the form itself some space in between and then we just need a button, submit. Let's make it block. Okay. Class equals call spin 2. Alright. So big giant form, now we need to submit the order, submit order, await, direct us, use orders, order, giveaway dot ID, create item, that looks correct. Let's wrap this in a try catch error console dot error. Alright. Time is a ticking. Let's see, we got 43 seconds. Let's see if we can actually get this done. Brian Gillespie, email bryantat directus.io. 123 Main Street, Bluefield, West Virginia. Submit. Oh, duh. Dummy. Come on now. Submit dot prevent. Oh, no. He's typo. Submit dot prevent. Submit order. One second. We hit the timer here right at the very end, just as we were getting really good. I just wanna submit this out and see if it's actually gonna work. Test city, 1232222. Submit. Did we get an error? Post local items orders equals forbidden. So I'm getting that error because there are permission settings that we didn't control order items. We forgot to set that up. But now if we were to go back and just try this again, orders forbidden. Yeah. So, something in the way that I'm passing the data here, don't have permissions to access this. It is probably something to do with the person data that I did not get updated. And it's actually what? State Street Zip. This is the this is the main issue why you shouldn't trust AI. In our hurry, this actually should have been order dot address dot street dot city dot state, all of that. Didn't even pick it up. Hopefully, you're not coding against the clock like I am, but that's one of the the issues there. Test, test, test, test, submit. Still getting some issues. So, we tried our best. I still think this looks pretty good. And with another 20 minutes or so, 10 minutes or so, we could have completed this out where it would actually submit the order to Directus. But I hope this is a good example of just how quickly you can build apps with Directus, and a a front end framework like Nuxt. Directus gives you a lot out of the box, which makes speed runs like this possible. This was a lot of fun. I don't think that I'm gonna be replacing swag.com anytime soon, but, nevertheless, if you had different needs or you wanted your own customized platform, you could quickly and easily build it with Directus and have something that was specific to your company. These platforms like swag.com and others are obviously, they have a lot going on on the fulfillment side. But with Directus you could build custom ecommerce like this, custom experiences for people rapidly. So that is it for this episode of 100 apps 100 hours. Thanks for sticking it out with me and I hope you'll tune in for the next episode. Ciao.","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,122,160,161,162,163,164,165,166,167],"cb4e067f-9507-4e18-ab9a-435565f9e653","8434838a-8e4f-489a-8da2-fbf10de5de6a","1109be0d-8ab5-479b-a052-8ad30d9ffb1c","dcb952e0-7d13-43ea-9b1c-f2ca63efd07d","0aae287d-3916-4d91-9310-25828998e562","8fed0eee-43f6-4767-8b77-79da7a059821","a311b57d-34e7-4073-9cf3-6a8c6c0f8b85","9271a4fc-addf-4400-9591-f2f2ec07bd79","30c48566-52cd-4728-a633-ca9675acc959","c067c012-5fe1-4d70-8946-8bfc8e1b22c0",{"title":169,"tile":170},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"id":172,"slug":173,"season":174,"vimeo_id":175,"description":176,"tile":177,"length":178,"resources":8,"people":8,"episode_number":155,"published":179,"title":180,"video_transcript_html":181,"video_transcript_text":182,"content":8,"seo":183,"status":137,"episode_people":184,"recommendations":186},"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","crm","14fda5f2-95de-4dbe-a4e2-3fd956c21c19","936325383","In this intense one-hour challenge, watch as Bryant incredibly builds a full-featured custom CRM from the ground up using Directus. He builds contacts, organizations, deal pipelines, activities, and more.","f6b880a0-5cd2-45ad-beff-3117f0a78581",56,"2024-04-19","Mission: Customer Relationship Manager","\u003Cp>Speaker 0: Hi. Welcome back to another episode of 100 apps. 100 hours where we build some of your favorite apps or try to rebuild some of your favorite apps in 1 hour or less, or get publicly humiliated trying. Alright. Super excited to be back for another season.\u003C/p>\u003Cp>If you are new to 100 apps 100 hours, there are 2 basic rules. Number 1, you have 60 minutes to build and plan and build, no more, no less. So when that clock strikes 0, that's it. And then the second rule is there are no other rules. Use whatever you have at your disposal to complete the functionality.\u003C/p>\u003Cp>That's it. Let's dive into this episode. So today, I've got a custom CRM. Tools like Salesforce, Pipedrive, HubSpot, they really need no introduction. Everybody needs a CRM.\u003C/p>\u003Cp>A lot of them, depending on your purposes, may be way too much for what you need or they may not be specific enough for your industry. So we are going to build our own custom CRM in 1 hour or less. Let's do it baby. Alright. So, let's start the clock and away we go.\u003C/p>\u003Cp>So, when we plan our CRM, what do we need out of a CRM? Right? What kind of functionality do we want to see inside our CRM? So basically, we want to manage all of our contacts. We want to manage all the different organizations those contacts belong to.\u003C/p>\u003Cp>What else? We wanna manage our sales pipeline. Manage sales pipeline. That's gonna be deals or activities. We want to be able to track activities and follow-up.\u003C/p>\u003Cp>So this seems like a a pretty good set of functionality for a basic CRM. I'm sure we might embellish this a little bit depending on how far we get, but let's dive into actually fleshing out what the data model just might look like for something like this. So we'll drag a nice square up here. We're gonna have contacts. We could just call those people.\u003C/p>\u003Cp>I'm a big fan of that. We're gonna have organizations. There's definitely gonna be a relationship between those 2, but I I feel like this could be a many to many relationship because one contact could belong to multiple organizations, and and some CRMs make that a little more difficult to do than others. What else do we have? We're gonna have deals or opportunities.\u003C/p>\u003Cp>Deals is kind of a a standard nomenclature. We're gonna have activities that are attached to what, those are attached to organizations, they're attached to contacts, they're probably also gonna be attached to deals. What else are we going to have? We're probably going to have some sales reps, those are going to be our users inside our accounts. This looks pretty good for a a base set of functionality.\u003C/p>\u003Cp>Let's dive in and actually start building something. So, I'm just gonna pull up my Directus instance, that's what we're using on the back end. Totally blank instance, we'll zoom in on this, and let's start building. Right? Let's create our first collection.\u003C/p>\u003Cp>Let's start with contacts. So we're gonna give this a contacts as the name. For the primary key, let's use generated UUID. What kind of fields do we wanna add for our contacts? Right?\u003C/p>\u003Cp>When we think of contacts, we're gonna need name, email, but we may also wanna track when this was updated, when it was created. Directus makes that super easy. And one tip that you may not have realized if you've used Directus before, is I can go in and change these to be whatever I want. So if I wanted this to be created at and created by, I could go in and update those, change them to be whatever I want. Updated at, updated by.\u003C/p>\u003Cp>That's the the nomenclature that you're used to. Do we actually need a sort for our contacts? I don't think. Let's just go ahead and save this. Alright.\u003C/p>\u003Cp>So for our contacts, we're gonna add a first name. Great. We'll make that a string. Let's do half width for that. I can go in and click the three little dots here and just quickly duplicate this field to save myself some time.\u003C/p>\u003Cp>And, of course, as I am plotting away on this, Directus underneath is mirroring all these changes to my database schema. So we've got our first name, we've got our last name, we're gonna need a email or an email. Great. Let's require this value. And, you know, I could even go as far as, like, making this unique if I I wanted to have some type of unique identifier to match these up with.\u003C/p>\u003Cp>Alright. As far as an interface, you know, I could get fancy with this and we could add a little email icon like an at symbol. That's great. I think everything else is good. You know, one of the other nice things that's built into Directus is I can go in and add my own custom reg x validation.\u003C/p>\u003Cp>So, you know, I can put in some type of reg x that matches email addresses. I don't have one of those handy here, but we'll go ahead and keep marching along here. So we got first name, we got last name, we got email. We'll probably have a phone number at some point, but let's let's go for, like, a job title, probably. I'm trying to think of all the standard fields.\u003C/p>\u003Cp>Maybe we have some notes. We could set that to be a text area field. I don't really need formatting for those. Great. Job title.\u003C/p>\u003Cp>And maybe honestly, let let's strip this out because I'm I'm thinking about that many to many relationship with organizations, and we may have, different titles at different organizations. So I'll show you how we can handle that coming up. Alright. So we got first name, we got email. We could go ahead and add phone number as a string.\u003C/p>\u003Cp>Phone or phone number. Let's just keep it short. We'll say phone. Let's give it a phone icon. Great.\u003C/p>\u003Cp>K. Phone, email. We'll put email above phone. Cool. Looks nice.\u003C/p>\u003Cp>Alright. Let's just take a look. Right? We've got first name. Go ahead.\u003C/p>\u003Cp>Oh, I was gonna add my wife here, but she's gonna get mad at me if I misspell it. Right? Ashley atexample.com. I had a phone number, 555-5555. Here's some nice notes for our contact.\u003C/p>\u003Cp>These are looking great. Right? We've got our contact in here. I could potentially sort and filter this a a hundred different ways, maybe change the layout. But let's dive into the next collection that we wanna set up, our organizations.\u003C/p>\u003Cp>So we'll have organizations, again, for the primary key, I'm gonna use ID and just use generated UUID as the type. And, again, I could change created at I could change this structure for these system fields to be whatever I want. Created by, date updated, that's gonna be updated at, if I can actually spell. And we use this one as well and call it updated by. Great.\u003C/p>\u003Cp>Okay. So now we've got an organization. For our organization, what are we gonna have? We're gonna have a name of the organization. Right?\u003C/p>\u003Cp>Probably, some addresses for that organization. Right? We could have multiple. So that's where we might reach into an another bag of just a different table. What else are we gonna have for an organization?\u003C/p>\u003Cp>You know, let's just do something like country in case we want to, even, like, automatically route new organizations to a specific sales rep. Potentially, we've got a name. What are we gonna have? We're gonna have a website. You know, we may have a a logo if we wanna track that for a company.\u003C/p>\u003Cp>So let's do an image file. Let's call it logo. You know, you might even add things like brand color, things like that if if you're really in tracking that. And then we'll just add another section for notes. This is gonna be a text area field with the type text, and we'll hit save.\u003C/p>\u003Cp>So we've got our organizations. We've got our contacts. Let's make a link between the 2. Right? So how do we go about that?\u003C/p>\u003Cp>Depends on how you want the data to actually be structured. Right? In an application, depending on the setup, maybe a user can only belong to one specific organization. But often inside of CRM and maybe I am working with the local little league. I am a member of the board there, and then I'm also a developer advocate here at Directus or, you know, a a founder at Better Side Shop.\u003C/p>\u003Cp>So a lot of different relationships that is gonna be modeled with the mini to mini relationship inside Directus. Here's how we set that up. It's gonna be pretty easy. We'll just go to create field. We'll look for a many to many relationship.\u003C/p>\u003Cp>And because we're on organizations, our key here is gonna be contact for the or contacts, I should say. Our related collection is gonna be contacts and then we'll just go through and paint by numbers here. Do we want to show these in a list or a table? I'm good either way. We definitely want to show a link to that item so we can get to that specific contact, but I'm gonna pop open this advanced field creation mode just to show you that you do have control over the naming of the junction collection and the individual fields within that.\u003C/p>\u003Cp>So, the default setup here is just going to take this table name and this table name or this collection name and this collection name and marry the 2 together and we end up with organizations underscore contacts. Works for me. We'll leave that autofill set up. And then on the reverse, I'm gonna add that many to many field to the contacts as well. We're gonna keep that as the organizations.\u003C/p>\u003Cp>Alright? In this case, we may have a sort field. So we wanna control the the sorting for contacts, like who's 1st, 2nd, 3rd, that could be helpful in, like, a primary contact situation. And then we have some of our relational triggers. Right?\u003C/p>\u003Cp>On deselect of organizations' contacts, what do we want to do here? Yeah. Maybe I want to delete that association. So I'll set these to cascade. Great.\u003C/p>\u003Cp>So now that back out, we can see we've got our contacts field here. If we go into, yep, looks like we've got, an extra contact table created. I might have typed something wrong. But, so we can see contacts there in the organizations. And then on our contact, I should see organizations here.\u003C/p>\u003Cp>Let's just add an organization. Right? So let's say my wife worked at Tesla. That's in the United States. And, you know, we could add the website, logo, notes.\u003C/p>\u003Cp>I could choose existing contacts. But I'm just going to go ahead and say 1. Right? So now, I could see I've got that organization here, but it's showing an ID, which is not super helpful. Right?\u003C/p>\u003Cp>So I can go into the organization itself and control the display template where I have a name. I could even potentially add the logo to that if I wanted. So now if we check it again, we can see, okay, here's the organizations that they're a part of. Right? Now, you also have the ability to manage the data inside that junction collection.\u003C/p>\u003Cp>Right? So if I go in and and like I said, I may have a different job title at all these different organizations. So I could go in and add a new field here inside this junction collection for job title. Great. Alright.\u003C/p>\u003Cp>So, now if we go into Tesla, we can see the job title is down here at the bottom, but Directus also gives me an easy way to control where that displays. So if I go to our many to many relationship inside our contacts, I can go to maybe we wanna add a sort field for this just to make sure. And then if I go to the interface, I can control where my junction fields are located. So I can put this at the top, which should be great. Okay.\u003C/p>\u003Cp>So now if we just check that out one more time. Right. Now I could see director of DirectUs. So now I could give a specific job title to this specific person within that organization. I could go in and add a new organization as well, like, hey, the little league.\u003C/p>\u003Cp>We can say a board member. Right? Little League Baseball is the name of the organization. Great. Okay.\u003C/p>\u003Cp>So now there my wife is a part of 2 organizations, different job titles for each. Right? Alright. How are we looking on time? We're looking great.\u003C/p>\u003Cp>Let's move on to our next collection that we want to set up. That's gonna be what? Our deals, our activities, what do we want to set up next? Let's go for deals or opportunities. Deals is probably the standard naming for this.\u003C/p>\u003Cp>So that's what we'll stick with. Could be opportunities. Could be something else. That's fine. We'll do created at.\u003C/p>\u003Cp>Just to keep the same structure, we'll do created by. And by adding these, whenever this record is created, it's going to populate a timestamp and a user. The same thing for Updated, whenever it gets updated or who updates it, it's gonna populate that info for us. Updated by. Great.\u003C/p>\u003Cp>Okay. So our deals what we're gonna have for our deal? We'll probably have a name or a title of the deal. Deal name sounds great. It could be good to prefix some of these sometimes if you're dealing with a lot of nested relational data.\u003C/p>\u003Cp>So you get used to seeing name in there a 100 times. Could be confusing whether that's the actual deal name or the organization name or the contact name. So we'll just save the deal name. What else are we gonna have on this deal? Right?\u003C/p>\u003Cp>We'll probably have a average dollar amount or a potential dollar amount. Let's set that to be, decimal. Deal value. We can add a nice little dollar sign to the icon to make this look nice and pretty. And then we are going to have some related fields.\u003C/p>\u003Cp>But before we do that, let's just add, like, some deal notes or something like that. Sounds great. Okay. So we got a deal name, we got a deal value, you know, maybe these are side by side, not a big deal. Let's go in, and now we want to add a relationship to the organization and probably to our primary contact for this deal.\u003C/p>\u003Cp>So those are both gonna be many to one relationships. And we'll set a key of organization here. So the related collection, we'll set that to be organizations. Great. And I could control the display template here.\u003C/p>\u003Cp>We'll just use the name and we hit save. So now we've got an organization that we're gonna tie to the deal. I'll make that half width. And then we also want to add a primary contact. So we'll just say primary contact.\u003C/p>\u003Cp>The related collection here is going to be the Contacts collection. And for the Display template, let's use first name, last name, and let's use this format, which I think is typically how it displays inside an email client where you have the email address inside these less than or greater than SQL or symbols. So let's save that. We've got our primary contact. We've got our notes.\u003C/p>\u003Cp>We've got our deal. What do we need next? Right? We need to track where that deal is at. So we could use, like, a a status field as, like, a drop down for that potentially.\u003C/p>\u003Cp>Right? What's the status of this particular deal? Blah blah blah. Or we could do something else where we have a relationship to a deal stage or our individual pipeline. Right?\u003C/p>\u003Cp>Because then we may want to, potentially have separate pipelines or we we wanna give more control back to our users, so the the sales reps or the sales manager to control that actual pipeline without getting into the admin section. So how can we do that? Right? Looks like I've got a little extra couple of fields that I've been creating here inadvertently. Alright, so now let's add a new collection.\u003C/p>\u003Cp>Let's just call it Deal Stages. We want to manage what are the stages of a particular deal. As far as the optional fields, maybe I don't really not super concerned with these, deal stages. We're gonna call this the name of this particular stage. Maybe we wanna give it a color so we can add some color to it.\u003C/p>\u003Cp>And, you know, I guess we could even give it an icon if we wanted to. We could play around with that and see what that looks like. Deal stages. Let's make both of those half width. And, now, let's just go through and map some of these particular stages.\u003C/p>\u003Cp>Right? And I and I, you know, I've got this s on here. One of the other things that you could do inside Directus, you can't control the translations for all of these. So even if you're working in English, on the the back end, you may want to use prefix tables to keep things nice and organized as a developer. This is a great way to control what displays via the interface to your actual users within the application.\u003C/p>\u003Cp>So I could just call this deal stages or pipeline stages, whatever makes a lot of sense here, and hit save. Alright. So it still shows me deals_stages here, but when I start looking inside the actual app, there we go, we can see our pipeline stages. So, let's create our first one. Right?\u003C/p>\u003Cp>Let's just say, New, this is inbound. Do we have a symbol? We do have a symbol for this. Right? So let's say red.\u003C/p>\u003Cp>This is we need to take action on this. What's next? Right? Qualified, maybe? Contacted?\u003C/p>\u003Cp>Or, let's say assigned. Right? We forgot to assign those to a sales rep. Assign assignment. That's a nice looking icon for that.\u003C/p>\u003Cp>Then we'll say qualified. You know, if we're doing software sales, like, a check mark. Okay. What else? Demo.\u003C/p>\u003Cp>Let's save green. Oh, let's make this orange. Do we have like a demo? Maybe a TV? What have we got?\u003C/p>\u003Cp>Yeah, there we go. That works for a demo as far as the icons. And then we are going to set, like, a last stage, like, proposal. Great. Let's just go gray.\u003C/p>\u003Cp>Cool. Document. Awesome. Alright. So I can control the way that these are displayed through my actual settings, here.\u003C/p>\u003Cp>So I can go into each specific one. And on the display tab, let's display a colored dot for the color. And then for the icon, we're just gonna display the actual icon. Alright. Great.\u003C/p>\u003Cp>If we take a look now, I can see what these actually look like. Cool. And then for my display template for the deal stages, I may even go in and just mirror that same structure where I have a color, then I have an icon with a name. Okay. So, now, what we've done, we've effectively given control over these, and we probably actually need a sort on these as well, so so we can control that value.\u003C/p>\u003Cp>So, let's go in and quickly add a sort. That's going to be an integer value. And because this is coming after the fact, after we've created this actual table, I'm just gonna scroll down a little bit, and I will have a sort field here. So we'll choose sort just to make sure we manage that. And now I should be able to drag and drop these, in whatever order that I like.\u003C/p>\u003Cp>Again, we're giving control back to the users of our custom CRM. We're definitely gonna prevent them from getting inside the admin so they don't mess with the data model. But here, we give them the ability to control what those pipeline stages are gonna be. So now we need to add those to our actual deals. Right?\u003C/p>\u003Cp>So we're gonna go into our deals. We're going to create a many to one relationship. And, you know, this is going to be the deal stage or just stage. And we're gonna use deals underscore stages as the related collection. We can open this up and and just see what's going on.\u003C/p>\u003Cp>I I don't really need to create that inverse relationship. I could. Doesn't make a ton of sense, though. I'm not sure. Alright.\u003C/p>\u003Cp>Display related values, validation. Let's keep it very simple, and then we're gonna use our deal stage. Alright. So now if I go into our deals Why is this oh, it goes in the machine. I don't know what's going on on this particular example, but it keeps creating additional fields that we don't need.\u003C/p>\u003Cp>Alright. So let's create a new deal. Alright. This is gonna be a new deal. We can see, hey, there's our stage.\u003C/p>\u003Cp>That looks great. The deal name is 100 apps, 100 hours contract. It's worth $1,000,000. So we'll add that. How many zeros do we have there?\u003C/p>\u003Cp>1, 2, 3, 4, 5, 6. Alright. Let's make this for the Little League team. Great. My wife is the primary contact.\u003C/p>\u003Cp>Here's some notes on this deal. Great. Did we save it? Numeric value and deals is out of range. Uh-oh.\u003C/p>\u003Cp>Wouldn't be a 100 apps, a 100 hours without, some type of issue. Right? So let's take a look at this. The deal value, what did I set? Precision and scale.\u003C/p>\u003Cp>Maybe we bumped this up way up. Probably could have gone with integers for this as well. Do I have a minimum and a maximum? I I don't have that set. So not entirely sure what's going on.\u003C/p>\u003Cp>Let's just try this again. 100 apps, 100 hours contract, 1 100,000, 1,000,000. Try it again. Save. Okay.\u003C/p>\u003Cp>Alright. So now, we've got this particular deal set up. This is kind of an underwhelming view. Right? And, again, I might control how these things display.\u003C/p>\u003Cp>So if I just go into our deals, we go to the deal stage. If I wanna control how it displays, I can go in and set this. So let's do the color, do the icon, do the name, just because, I'm a I'm a very visual person, and I suspect, a lot of your end users may be as well. We still don't see the organization there. So, again, we'll go back.\u003C/p>\u003Cp>We'll adjust our organization. We wanna display related values. We want to show the name of that specific organization. And let's get even fancier and maybe we wanna show that logo. I'm gonna click on this and then you'll see this one that has a little magic beside it that's a thumbnail.\u003C/p>\u003Cp>That's just a shortcut for, like, a nice thumbnail sized image instead of loading the actual image size. So I donit have a logo for this company, but I could quickly find 1. Alright. So, we'll just copy this image address, go back to our instance, let's load up that specific company, they who shall not be named, Hit import. Oh, that's a data URL.\u003C/p>\u003Cp>Let's find an actual URL that we can just copy. Great. Let's try this again. We can import via URL. Cool.\u003C/p>\u003Cp>And now when I'm looking at my deals, I can see the logo of that organization. That makes it really handy, when I'm working on a deal just to have that extra extra visual reinforcement. Now, when I'm looking at deals, I may want to set up like a traditional pipeline type of view. So I could do that really easily just by switching the layout here inside Directus. So we'll change this to a Kanban layout.\u003C/p>\u003Cp>Let's group by the deal stage and the group title is gonna be the name. So I can see new assigned qualified demo. We have that Kanban view that we're used to. As far as the text, what do we wanna look at? We want to control, let's say, when this was created.\u003C/p>\u003Cp>I'm not really concerned about that. The tags, what do we want to set that to be? Do we want a card image? Honestly, this looks okay. Do we need an actual user on here?\u003C/p>\u003Cp>Probably not. Cool. Alright. We'll just keep it as is and let's let's start fleshing this out a little more as well. Right?\u003C/p>\u003Cp>So within a deal, when I'm working this deal, we're going to have activities assigned to this as it moves across the pipeline. And, for that, let's just go in and add a new table, a new collection, we're going to call it activities. Activities, I think that's the correct spelling. We use generated UID, we'll do created at, createdby, create up, updatedat, updated by. Great.\u003C/p>\u003Cp>And, in this case, maybe we do give a status for this particular activity. Right. Status, great. Let's give a name of the activity, probably a type of activity. Makes sense.\u003C/p>\u003Cp>We use a drop down for that. It seems pretty straightforward or, you know, maybe they're not gonna need to change the activity types on a a very frequent basis. So we'll call it the activity type, and let's add a couple choices to this. So this is a relatively new addition to Directus within the drop down, the ability to have an icon and a color. So let's say text is a phone call, value is a phone.\u003C/p>\u003Cp>One of the other cool things if you want to translate this value, you could use this phone t, or this dollar sign t for a translatable string. And then anytime you have users who are using the app within a different language, inside the settings, you can control all those custom translations. We'll take a look at that in a moment. Let's call this what? Phone call.\u003C/p>\u003Cp>The value could just be phone call, depending on how I wanna store this. Right? It may have an underscore within it as well. It could just be the same thing. Either way.\u003C/p>\u003Cp>Look for that phone. Great. And I could even add a color for that if needed. Maybe I just wanna keep these all the same. Phone call, we'll call it a meeting.\u003C/p>\u003Cp>And for this, do we have a meeting icon? Right? We calendar looks nice. Maybe we want to track demos separate from meetings. Alright.\u003C/p>\u003Cp>So we drag a demo. That was the TV icon that we had. Great. What else are we gonna need? Like, an email?\u003C/p>\u003Cp>Not sure I would Yeah. Maybe, like, a follow-up email. We wanna reschedule that. And then we'll do an email. Great.\u003C/p>\u003Cp>Demo, phone call. I think I'm gonna set this to be underscore value. Cool. Alright. Looking good.\u003C/p>\u003Cp>Activity type. Okay. What else are we going to need for this? We need a due date for this activity and the status, we can use as as is. It's published draft, archived.\u003C/p>\u003Cp>What do I really care about this activity? It probably isn't completed or not. Right? So maybe we scrap status, and maybe we just go for a toggle instead. Right?\u003C/p>\u003Cp>Hey. Is this completed? The default value, great. For our label, maybe we change it to completed and give it a color of color on. Color off is red, just to show that it has not been completed.\u003C/p>\u003Cp>Great. And let's just clean up this form a little bit, making sure everything looks nice. Okay. We are going to add a date for this, and let's set a specific date and time that this thing occurred. Call it due date, end date.\u003C/p>\u003Cp>Due date seems reasonable, but when do we actually need to complete this specific task? Okay. So now we've got an activity. We want to, link this to our actual deal so we can track those. Right?\u003C/p>\u003Cp>So, what I'm gonna do in this case is create a mini to one relationship because this activity could only belong to a single deal. Right? We're gonna use the key of deal. The related collection, we'll set that to deals. We'll show the name of that deal.\u003C/p>\u003Cp>Maybe we show the actual organization as well so I can actually dig into the related collections and and show values from that? Excuse me one second. Sorry about that. Cool. Alright.\u003C/p>\u003Cp>So now, what I forgot to do is create that inverse relationship. You can actually set that up via direct us when you're creating that relationship. But now I can also just go into our deals, not deal. We'll go into our deals, and now we're gonna create a one to many relationship back to those activities. So we'll call this key of activities.\u003C/p>\u003Cp>We've got activities. The foreign key will be deal. That already exists. Maybe we wanna show these in a table. We'll choose the columns, due date, Name, Due Date, Activity Type.\u003C/p>\u003Cp>Seems pretty savvy. And I can even filter these, right? So if I wanted to see just activities where they were not completed. We can enable search and filtering and show a link to these. We'll take a look at what all these look like in just a moment.\u003C/p>\u003Cp>But we've forgotten one important step through this whole process is, hey. We need somebody to assign these deals to. Right? So, let's add a many to one relationship. We'll call that the deal owner or, you know, you potentially say who this is assigned to.\u003C/p>\u003Cp>Like, the deal owners, again, kind of standard naming in these scenarios. And for the related collection here, we're gonna use directus underscore users. So these are gonna be actual users of the application that we're assigning this to. Invalid payload collections can't start with direct us users. Oh, deal owner.\u003C/p>\u003Cp>Let's go to our related collection, and let's get direct us underscore users. And, in this case, we're gonna show the first name, last name. We may back up and do an avatar as well. So just the the thumbnail, the avatar, I could move these around just by using edit raw value. You'll see these are just, the standard mustache syntax that you see throughout Directus as well.\u003C/p>\u003Cp>Excuse me. Let me get this a drink of water. I'm actually dying. That's a turn of fate. Okay.\u003C/p>\u003Cp>Alright. So we're gonna save this. Deal owner already exists in deals. Okay. Alright.\u003C/p>\u003Cp>Great. Let's move this around. Maybe we make deal stage half width. We slide deal owner up there. Let's actually take a look at this now.\u003C/p>\u003Cp>I should be able to assign folks. So let's create a new user. We'll just call it sales rep. Salesrep@example.com. And maybe we give them a nice avatar.\u003C/p>\u003Cp>Right? Sales rep, avatar. Let's just see what Google comes back with. John's inside sales rep. Yeah.\u003C/p>\u003Cp>This looks this is perfect. This is my guy right here. Alright. There's his avatar. We'll just save that.\u003C/p>\u003Cp>And now we can see who we've assigned this particular deal to. And now maybe within the deal card user here, I wanna show who that deal owner is. Great. Mister sales rep. Looking good.\u003C/p>\u003Cp>Alright. One of the other things that we need to do on our activities, we probably got a an owner of that activity or assigned to. It's been assigned to somebody on the team. Again, that's going to be assigned to a direct us user. We'll save that.\u003C/p>\u003Cp>And lots going on here. Just some type of weird glitch. I could see a couple of extra collections. I'll just remove these. Alright.\u003C/p>\u003Cp>Cool. So now weive got a deal. Weive got a table full of activities. I can go ahead and add these, like, follow-up on proposal. This is assigned to Mr.\u003C/p>\u003Cp>Sales Rep. We've got the activity type. We can see that's going to be, just a quick phone call. This is completed. We can see that conditional, conditional formatting for that.\u003C/p>\u003Cp>And we can add a due date of, let's say, the next Friday. Great. Save that. Keep editing. Cool.\u003C/p>\u003Cp>Alright. So, now, we've got the basic inner workings of a CRM. Right? We've got our deals, we've got contacts, we've got organizations, we've got our different pipeline stages. Right?\u003C/p>\u003Cp>If I wanted to organize these things a bit, we could go in and add different icons for each of these. So, you know, maybe we set some people icons for our contacts. We've got our organizations. Do we have an organization option? Let's look and see.\u003C/p>\u003Cp>Business. Is there a business? There you go. That looks somewhat like a business. We've got activities.\u003C/p>\u003Cp>Maybe this will be like a checklist. Cool. We've got our deals. Let's make those the money. Dollar signs, that's great.\u003C/p>\u003Cp>And then, deal stages, to me this is like a settings. Right? So I could create a new folder, let's just call it settings folder. You don't necessarily have to add this, but maybe we just do to keep it clean. And we look for, like, a settings icon just to use here.\u003C/p>\u003Cp>That's great. This one looks magical. Settings suggest. Right? And, again, I can change the name of this to just say settings.\u003C/p>\u003Cp>So, it still creates this collection, but, we can call it whatever we want. So we'll drag this up within settings folder. We'll drag deal stages and, this will be, what, like the Kanban view. There we go. Awesome.\u003C/p>\u003Cp>Okay. So now we get a little more organization to this. One of the other things that you might do and, you know, that you use all the time within ACRM are saved views. Right? So Directus gives you that ability with bookmarks.\u003C/p>\u003Cp>We'll just go in to the top here and maybe I want to sort by a specific sales rep. Right? Like, the deal owner is, a specific person and specific name is sales rep. That's the only one at this point. But I could go in and create a bookmark for this, and we could call it, deals sales rep Man.\u003C/p>\u003Cp>And we can change this up, give it a color. Now, within that collection, now we can see we've got our deals for Sales Rep Van, and I could save that bookmark. So even if I go into the main deals view, and maybe we change this back to a table view, which could be easier for, you know, maybe a sales manager or something who's controlling this. So I had deal owner back to this as well. Now if I go back, deal sales rep man view, boom, there it is.\u003C/p>\u003Cp>It's saved. I could go in and update this if I wanted to as well. So now we've got our pipeline. When we go into each one of the deal, we have our name, we've got our organization, we've got the notes, we've got our activities. You know, we can mark these activities off as completed.\u003C/p>\u003Cp>That seems like a great CRM baseline. Let's let's take a look at where we're at. Right? We got, like, 16 minutes on the clock. This feels like a win.\u003C/p>\u003Cp>I don't I don't know if we wanna run that one, let's discuss where we could go from here, right, maybe we want to automatically send some emails when it hits a certain stage in the pipeline. So let's just call these things done. Right? We can manage all of our contacts. We can manage all of our organizations.\u003C/p>\u003Cp>We can manage our sales pipelines. We can track our activities and follow-up. So let's say, you know, we get a new deal inbound. Maybe we want to automatically assign that to a a particular person, or we want to send a notification to our sales rep when that assignment happens. Let's figure out how to do that.\u003C/p>\u003Cp>Right? So I'm gonna go in. Let's just create a new deal. We'll say actually, let's wait a moment. Let's go into our flows.\u003C/p>\u003Cp>This is a good example. Right? Whenever a a new deal comes inbound, we want to send an email notification to our sales rep to to let them know. Alright. So we're gonna create a new flow.\u003C/p>\u003Cp>We'll just call it new inbound deal. Pretty straightforward. We could change this to the new symbol if we want to and just do a trigger setup. So what are we going to choose here? Directus gives you a ton of different options, as far as what to use when you're creating a flow.\u003C/p>\u003Cp>In this case, we're going to use the event hook. So when a certain event happens inside the platform, we wanna trigger an automation. The type that we're gonna choose here is action non blocking because we don't, the the filter allows you to basically either adjust the payload when a new deal gets created or a new event happens. Action non blocking, again, that runs after a create or update action. So in this case, let's do the items dot create.\u003C/p>\u003Cp>Whenever we're gonna trigger this based on the deals collection. And cool. Alright, so now I'm just going to save this, right? I'm going to go in and let's create a new deal inside the system. It's in the new stage.\u003C/p>\u003Cp>We're gonna assign this to mister sales rep. New deal automation. And we add, let's set this one to be for the little league. Again, we choose a specific contact, add some notes, and, maybe, the deal value, we'll just ballpark it at $5,000. Great.\u003C/p>\u003Cp>If I go back, now I can see that in my logs, I've got this flow. Here's the payload of this particular flow, and we could see who the deal owner is on this particular deal. How do we send an email notification to that specific owner? Right? I'm just gonna take this, copy it, and I open up just my Versus code editor where I've got my 100 apps, just, Docker Compose file here set up to run this locally.\u003C/p>\u003Cp>I'm just going to save that in case I need it. And, next, let's flesh this out a little bit. Right? So you can see the data we're getting back here. We're probably gonna need to look up that specific user.\u003C/p>\u003Cp>We can find them there. Those are the deal owner. We could send them a notification inside the app or we could send them a notification via email. Right? There's 2 options there.\u003C/p>\u003Cp>We've got notification. In this case, we've got the UUID of the user that we're going to send that to. So, you know, potentially, you want to send that in app. App. In this case, if there's a new deal, they're probably gonna be in their inbox.\u003C/p>\u003Cp>We're going to send that new deal to them. But let's actually find that email address first, though. Alright. So we're gonna read data from a specific collection. Let's call it Find User is the actual step we're gonna do here.\u003C/p>\u003Cp>For the permissions, let's just give full access. And, for the collection, you can see I don't have the Directus Users collection here, but I can go in and edit my raw value and just use Directus underscore users. And for our IDs, right, what are we gonna put here? So if I open this back up, we're gonna use the trigger. Payload.\u003C/p>\u003Cp>Dealowner. So we'll do this, we'll do trigger dot payload dot deal underscore owner. And I'm gonna wrap this in mustache syntax. And let's try this again. So we'll read the user.\u003C/p>\u003Cp>Let's go ahead and maybe just add this send email as well. So we'll send the email, and this is gonna be the read underscore user. We're using the key that we set of the previous operation within that flow. Readuser. Email should be.\u003C/p>\u003Cp>We'll input that. New deal assigned to you. And hey. Read_user.firstname. We've assigned a new deal to you.\u003C/p>\u003Cp>And then I can even go through and add those different variables if I wanted to for things like the deal name or the deal ID and and add a link back to that. So let's just try trigger. Payload.deal_name. Great. We'll save it.\u003C/p>\u003Cp>And now let's just test this out. I'm not actually sure if I've got emails set up here locally on this particular instance though, so that could be fun. But we should be able to actually see if this runs. In light of that bit of news, because it just looking at my configuration here, I do not have email configured here locally. So let's let's detach that one, and let's just test the notifications.\u003C/p>\u003Cp>Send notification. Cool. Send notification. The find_user.id. Cool.\u003C/p>\u003Cp>Full access. And it was a new deal assigned. We'll just add the key here. So that'll be trigger dot payload. Or no.\u003C/p>\u003Cp>Actually, it may be something like this where we have key. Alright. Let's just take a look just to make sure. Alright. Within our payload, we can see the key there.\u003C/p>\u003Cp>That's good. In that case, we probably didn't need to get the actual user there, but that's okay. I'm just gonna copy this message that we set here. Just paste that. And let's take a look at where this gets us.\u003C/p>\u003Cp>Alright. So we've got a new inbound deal. We're going to read the data of the user that we've assigned that to and we're going to send a notification to that specific person. Alright. Now we've got a new deal, deal stage.\u003C/p>\u003Cp>Let's just set it to new. Deal owner. I'm gonna choose myself here so we could test this notification. Say deal name. Test deal.\u003C/p>\u003Cp>It's worth $6,000,000. Very nice deal. We got a primary contact. Here's some notes. We'll just save that and let's see what happens.\u003C/p>\u003Cp>Right? We go to our Flow, we get a new inbound deal, we can see our logs, send notification, recipient is so and so. Hey, undefined. We've sent you a new test deal. Underscore first name.\u003C/p>\u003Cp>Why did that not come back? Read underscore oh, that's why I used the wrong key. Sometimes that happens. Find user dot first name. Great.\u003C/p>\u003Cp>So now, if I look and I check my activity log, I can see that I've got this new deal signed. Here's the notification for that. And if I click on it where it says view content, it should take me to that specific test deal. Great. Awesome.\u003C/p>\u003Cp>Let's call that a win. Right? We've got our custom CRM built out. We've set up some automation for this. We could go further and flesh this out a a ton of different ways.\u003C/p>\u003Cp>Right? If we talk about it, like, we could go through and send emails to our actual primary contact when it reaches a certain stage, that would be fairly easy to do using direct dis flows. You know, we could maybe even go as far as, like, a future future iteration of this could be setting up an inbox to parse incoming emails like a BCC functionality and add these to those specific deals as well. But this this feels really good. I'm I'm calling this a win.\u003C/p>\u003Cp>That's our custom CRM. Thanks for joining me on this episode of 100 apps, 100 hours. We'll catch you on the next one.\u003C/p>","Hi. Welcome back to another episode of 100 apps. 100 hours where we build some of your favorite apps or try to rebuild some of your favorite apps in 1 hour or less, or get publicly humiliated trying. Alright. Super excited to be back for another season. If you are new to 100 apps 100 hours, there are 2 basic rules. Number 1, you have 60 minutes to build and plan and build, no more, no less. So when that clock strikes 0, that's it. And then the second rule is there are no other rules. Use whatever you have at your disposal to complete the functionality. That's it. Let's dive into this episode. So today, I've got a custom CRM. Tools like Salesforce, Pipedrive, HubSpot, they really need no introduction. Everybody needs a CRM. A lot of them, depending on your purposes, may be way too much for what you need or they may not be specific enough for your industry. So we are going to build our own custom CRM in 1 hour or less. Let's do it baby. Alright. So, let's start the clock and away we go. So, when we plan our CRM, what do we need out of a CRM? Right? What kind of functionality do we want to see inside our CRM? So basically, we want to manage all of our contacts. We want to manage all the different organizations those contacts belong to. What else? We wanna manage our sales pipeline. Manage sales pipeline. That's gonna be deals or activities. We want to be able to track activities and follow-up. So this seems like a a pretty good set of functionality for a basic CRM. I'm sure we might embellish this a little bit depending on how far we get, but let's dive into actually fleshing out what the data model just might look like for something like this. So we'll drag a nice square up here. We're gonna have contacts. We could just call those people. I'm a big fan of that. We're gonna have organizations. There's definitely gonna be a relationship between those 2, but I I feel like this could be a many to many relationship because one contact could belong to multiple organizations, and and some CRMs make that a little more difficult to do than others. What else do we have? We're gonna have deals or opportunities. Deals is kind of a a standard nomenclature. We're gonna have activities that are attached to what, those are attached to organizations, they're attached to contacts, they're probably also gonna be attached to deals. What else are we going to have? We're probably going to have some sales reps, those are going to be our users inside our accounts. This looks pretty good for a a base set of functionality. Let's dive in and actually start building something. So, I'm just gonna pull up my Directus instance, that's what we're using on the back end. Totally blank instance, we'll zoom in on this, and let's start building. Right? Let's create our first collection. Let's start with contacts. So we're gonna give this a contacts as the name. For the primary key, let's use generated UUID. What kind of fields do we wanna add for our contacts? Right? When we think of contacts, we're gonna need name, email, but we may also wanna track when this was updated, when it was created. Directus makes that super easy. And one tip that you may not have realized if you've used Directus before, is I can go in and change these to be whatever I want. So if I wanted this to be created at and created by, I could go in and update those, change them to be whatever I want. Updated at, updated by. That's the the nomenclature that you're used to. Do we actually need a sort for our contacts? I don't think. Let's just go ahead and save this. Alright. So for our contacts, we're gonna add a first name. Great. We'll make that a string. Let's do half width for that. I can go in and click the three little dots here and just quickly duplicate this field to save myself some time. And, of course, as I am plotting away on this, Directus underneath is mirroring all these changes to my database schema. So we've got our first name, we've got our last name, we're gonna need a email or an email. Great. Let's require this value. And, you know, I could even go as far as, like, making this unique if I I wanted to have some type of unique identifier to match these up with. Alright. As far as an interface, you know, I could get fancy with this and we could add a little email icon like an at symbol. That's great. I think everything else is good. You know, one of the other nice things that's built into Directus is I can go in and add my own custom reg x validation. So, you know, I can put in some type of reg x that matches email addresses. I don't have one of those handy here, but we'll go ahead and keep marching along here. So we got first name, we got last name, we got email. We'll probably have a phone number at some point, but let's let's go for, like, a job title, probably. I'm trying to think of all the standard fields. Maybe we have some notes. We could set that to be a text area field. I don't really need formatting for those. Great. Job title. And maybe honestly, let let's strip this out because I'm I'm thinking about that many to many relationship with organizations, and we may have, different titles at different organizations. So I'll show you how we can handle that coming up. Alright. So we got first name, we got email. We could go ahead and add phone number as a string. Phone or phone number. Let's just keep it short. We'll say phone. Let's give it a phone icon. Great. K. Phone, email. We'll put email above phone. Cool. Looks nice. Alright. Let's just take a look. Right? We've got first name. Go ahead. Oh, I was gonna add my wife here, but she's gonna get mad at me if I misspell it. Right? Ashley atexample.com. I had a phone number, 555-5555. Here's some nice notes for our contact. These are looking great. Right? We've got our contact in here. I could potentially sort and filter this a a hundred different ways, maybe change the layout. But let's dive into the next collection that we wanna set up, our organizations. So we'll have organizations, again, for the primary key, I'm gonna use ID and just use generated UUID as the type. And, again, I could change created at I could change this structure for these system fields to be whatever I want. Created by, date updated, that's gonna be updated at, if I can actually spell. And we use this one as well and call it updated by. Great. Okay. So now we've got an organization. For our organization, what are we gonna have? We're gonna have a name of the organization. Right? Probably, some addresses for that organization. Right? We could have multiple. So that's where we might reach into an another bag of just a different table. What else are we gonna have for an organization? You know, let's just do something like country in case we want to, even, like, automatically route new organizations to a specific sales rep. Potentially, we've got a name. What are we gonna have? We're gonna have a website. You know, we may have a a logo if we wanna track that for a company. So let's do an image file. Let's call it logo. You know, you might even add things like brand color, things like that if if you're really in tracking that. And then we'll just add another section for notes. This is gonna be a text area field with the type text, and we'll hit save. So we've got our organizations. We've got our contacts. Let's make a link between the 2. Right? So how do we go about that? Depends on how you want the data to actually be structured. Right? In an application, depending on the setup, maybe a user can only belong to one specific organization. But often inside of CRM and maybe I am working with the local little league. I am a member of the board there, and then I'm also a developer advocate here at Directus or, you know, a a founder at Better Side Shop. So a lot of different relationships that is gonna be modeled with the mini to mini relationship inside Directus. Here's how we set that up. It's gonna be pretty easy. We'll just go to create field. We'll look for a many to many relationship. And because we're on organizations, our key here is gonna be contact for the or contacts, I should say. Our related collection is gonna be contacts and then we'll just go through and paint by numbers here. Do we want to show these in a list or a table? I'm good either way. We definitely want to show a link to that item so we can get to that specific contact, but I'm gonna pop open this advanced field creation mode just to show you that you do have control over the naming of the junction collection and the individual fields within that. So, the default setup here is just going to take this table name and this table name or this collection name and this collection name and marry the 2 together and we end up with organizations underscore contacts. Works for me. We'll leave that autofill set up. And then on the reverse, I'm gonna add that many to many field to the contacts as well. We're gonna keep that as the organizations. Alright? In this case, we may have a sort field. So we wanna control the the sorting for contacts, like who's 1st, 2nd, 3rd, that could be helpful in, like, a primary contact situation. And then we have some of our relational triggers. Right? On deselect of organizations' contacts, what do we want to do here? Yeah. Maybe I want to delete that association. So I'll set these to cascade. Great. So now that back out, we can see we've got our contacts field here. If we go into, yep, looks like we've got, an extra contact table created. I might have typed something wrong. But, so we can see contacts there in the organizations. And then on our contact, I should see organizations here. Let's just add an organization. Right? So let's say my wife worked at Tesla. That's in the United States. And, you know, we could add the website, logo, notes. I could choose existing contacts. But I'm just going to go ahead and say 1. Right? So now, I could see I've got that organization here, but it's showing an ID, which is not super helpful. Right? So I can go into the organization itself and control the display template where I have a name. I could even potentially add the logo to that if I wanted. So now if we check it again, we can see, okay, here's the organizations that they're a part of. Right? Now, you also have the ability to manage the data inside that junction collection. Right? So if I go in and and like I said, I may have a different job title at all these different organizations. So I could go in and add a new field here inside this junction collection for job title. Great. Alright. So, now if we go into Tesla, we can see the job title is down here at the bottom, but Directus also gives me an easy way to control where that displays. So if I go to our many to many relationship inside our contacts, I can go to maybe we wanna add a sort field for this just to make sure. And then if I go to the interface, I can control where my junction fields are located. So I can put this at the top, which should be great. Okay. So now if we just check that out one more time. Right. Now I could see director of DirectUs. So now I could give a specific job title to this specific person within that organization. I could go in and add a new organization as well, like, hey, the little league. We can say a board member. Right? Little League Baseball is the name of the organization. Great. Okay. So now there my wife is a part of 2 organizations, different job titles for each. Right? Alright. How are we looking on time? We're looking great. Let's move on to our next collection that we want to set up. That's gonna be what? Our deals, our activities, what do we want to set up next? Let's go for deals or opportunities. Deals is probably the standard naming for this. So that's what we'll stick with. Could be opportunities. Could be something else. That's fine. We'll do created at. Just to keep the same structure, we'll do created by. And by adding these, whenever this record is created, it's going to populate a timestamp and a user. The same thing for Updated, whenever it gets updated or who updates it, it's gonna populate that info for us. Updated by. Great. Okay. So our deals what we're gonna have for our deal? We'll probably have a name or a title of the deal. Deal name sounds great. It could be good to prefix some of these sometimes if you're dealing with a lot of nested relational data. So you get used to seeing name in there a 100 times. Could be confusing whether that's the actual deal name or the organization name or the contact name. So we'll just save the deal name. What else are we gonna have on this deal? Right? We'll probably have a average dollar amount or a potential dollar amount. Let's set that to be, decimal. Deal value. We can add a nice little dollar sign to the icon to make this look nice and pretty. And then we are going to have some related fields. But before we do that, let's just add, like, some deal notes or something like that. Sounds great. Okay. So we got a deal name, we got a deal value, you know, maybe these are side by side, not a big deal. Let's go in, and now we want to add a relationship to the organization and probably to our primary contact for this deal. So those are both gonna be many to one relationships. And we'll set a key of organization here. So the related collection, we'll set that to be organizations. Great. And I could control the display template here. We'll just use the name and we hit save. So now we've got an organization that we're gonna tie to the deal. I'll make that half width. And then we also want to add a primary contact. So we'll just say primary contact. The related collection here is going to be the Contacts collection. And for the Display template, let's use first name, last name, and let's use this format, which I think is typically how it displays inside an email client where you have the email address inside these less than or greater than SQL or symbols. So let's save that. We've got our primary contact. We've got our notes. We've got our deal. What do we need next? Right? We need to track where that deal is at. So we could use, like, a a status field as, like, a drop down for that potentially. Right? What's the status of this particular deal? Blah blah blah. Or we could do something else where we have a relationship to a deal stage or our individual pipeline. Right? Because then we may want to, potentially have separate pipelines or we we wanna give more control back to our users, so the the sales reps or the sales manager to control that actual pipeline without getting into the admin section. So how can we do that? Right? Looks like I've got a little extra couple of fields that I've been creating here inadvertently. Alright, so now let's add a new collection. Let's just call it Deal Stages. We want to manage what are the stages of a particular deal. As far as the optional fields, maybe I don't really not super concerned with these, deal stages. We're gonna call this the name of this particular stage. Maybe we wanna give it a color so we can add some color to it. And, you know, I guess we could even give it an icon if we wanted to. We could play around with that and see what that looks like. Deal stages. Let's make both of those half width. And, now, let's just go through and map some of these particular stages. Right? And I and I, you know, I've got this s on here. One of the other things that you could do inside Directus, you can't control the translations for all of these. So even if you're working in English, on the the back end, you may want to use prefix tables to keep things nice and organized as a developer. This is a great way to control what displays via the interface to your actual users within the application. So I could just call this deal stages or pipeline stages, whatever makes a lot of sense here, and hit save. Alright. So it still shows me deals_stages here, but when I start looking inside the actual app, there we go, we can see our pipeline stages. So, let's create our first one. Right? Let's just say, New, this is inbound. Do we have a symbol? We do have a symbol for this. Right? So let's say red. This is we need to take action on this. What's next? Right? Qualified, maybe? Contacted? Or, let's say assigned. Right? We forgot to assign those to a sales rep. Assign assignment. That's a nice looking icon for that. Then we'll say qualified. You know, if we're doing software sales, like, a check mark. Okay. What else? Demo. Let's save green. Oh, let's make this orange. Do we have like a demo? Maybe a TV? What have we got? Yeah, there we go. That works for a demo as far as the icons. And then we are going to set, like, a last stage, like, proposal. Great. Let's just go gray. Cool. Document. Awesome. Alright. So I can control the way that these are displayed through my actual settings, here. So I can go into each specific one. And on the display tab, let's display a colored dot for the color. And then for the icon, we're just gonna display the actual icon. Alright. Great. If we take a look now, I can see what these actually look like. Cool. And then for my display template for the deal stages, I may even go in and just mirror that same structure where I have a color, then I have an icon with a name. Okay. So, now, what we've done, we've effectively given control over these, and we probably actually need a sort on these as well, so so we can control that value. So, let's go in and quickly add a sort. That's going to be an integer value. And because this is coming after the fact, after we've created this actual table, I'm just gonna scroll down a little bit, and I will have a sort field here. So we'll choose sort just to make sure we manage that. And now I should be able to drag and drop these, in whatever order that I like. Again, we're giving control back to the users of our custom CRM. We're definitely gonna prevent them from getting inside the admin so they don't mess with the data model. But here, we give them the ability to control what those pipeline stages are gonna be. So now we need to add those to our actual deals. Right? So we're gonna go into our deals. We're going to create a many to one relationship. And, you know, this is going to be the deal stage or just stage. And we're gonna use deals underscore stages as the related collection. We can open this up and and just see what's going on. I I don't really need to create that inverse relationship. I could. Doesn't make a ton of sense, though. I'm not sure. Alright. Display related values, validation. Let's keep it very simple, and then we're gonna use our deal stage. Alright. So now if I go into our deals Why is this oh, it goes in the machine. I don't know what's going on on this particular example, but it keeps creating additional fields that we don't need. Alright. So let's create a new deal. Alright. This is gonna be a new deal. We can see, hey, there's our stage. That looks great. The deal name is 100 apps, 100 hours contract. It's worth $1,000,000. So we'll add that. How many zeros do we have there? 1, 2, 3, 4, 5, 6. Alright. Let's make this for the Little League team. Great. My wife is the primary contact. Here's some notes on this deal. Great. Did we save it? Numeric value and deals is out of range. Uh-oh. Wouldn't be a 100 apps, a 100 hours without, some type of issue. Right? So let's take a look at this. The deal value, what did I set? Precision and scale. Maybe we bumped this up way up. Probably could have gone with integers for this as well. Do I have a minimum and a maximum? I I don't have that set. So not entirely sure what's going on. Let's just try this again. 100 apps, 100 hours contract, 1 100,000, 1,000,000. Try it again. Save. Okay. Alright. So now, we've got this particular deal set up. This is kind of an underwhelming view. Right? And, again, I might control how these things display. So if I just go into our deals, we go to the deal stage. If I wanna control how it displays, I can go in and set this. So let's do the color, do the icon, do the name, just because, I'm a I'm a very visual person, and I suspect, a lot of your end users may be as well. We still don't see the organization there. So, again, we'll go back. We'll adjust our organization. We wanna display related values. We want to show the name of that specific organization. And let's get even fancier and maybe we wanna show that logo. I'm gonna click on this and then you'll see this one that has a little magic beside it that's a thumbnail. That's just a shortcut for, like, a nice thumbnail sized image instead of loading the actual image size. So I donit have a logo for this company, but I could quickly find 1. Alright. So, we'll just copy this image address, go back to our instance, let's load up that specific company, they who shall not be named, Hit import. Oh, that's a data URL. Let's find an actual URL that we can just copy. Great. Let's try this again. We can import via URL. Cool. And now when I'm looking at my deals, I can see the logo of that organization. That makes it really handy, when I'm working on a deal just to have that extra extra visual reinforcement. Now, when I'm looking at deals, I may want to set up like a traditional pipeline type of view. So I could do that really easily just by switching the layout here inside Directus. So we'll change this to a Kanban layout. Let's group by the deal stage and the group title is gonna be the name. So I can see new assigned qualified demo. We have that Kanban view that we're used to. As far as the text, what do we wanna look at? We want to control, let's say, when this was created. I'm not really concerned about that. The tags, what do we want to set that to be? Do we want a card image? Honestly, this looks okay. Do we need an actual user on here? Probably not. Cool. Alright. We'll just keep it as is and let's let's start fleshing this out a little more as well. Right? So within a deal, when I'm working this deal, we're going to have activities assigned to this as it moves across the pipeline. And, for that, let's just go in and add a new table, a new collection, we're going to call it activities. Activities, I think that's the correct spelling. We use generated UID, we'll do created at, createdby, create up, updatedat, updated by. Great. And, in this case, maybe we do give a status for this particular activity. Right. Status, great. Let's give a name of the activity, probably a type of activity. Makes sense. We use a drop down for that. It seems pretty straightforward or, you know, maybe they're not gonna need to change the activity types on a a very frequent basis. So we'll call it the activity type, and let's add a couple choices to this. So this is a relatively new addition to Directus within the drop down, the ability to have an icon and a color. So let's say text is a phone call, value is a phone. One of the other cool things if you want to translate this value, you could use this phone t, or this dollar sign t for a translatable string. And then anytime you have users who are using the app within a different language, inside the settings, you can control all those custom translations. We'll take a look at that in a moment. Let's call this what? Phone call. The value could just be phone call, depending on how I wanna store this. Right? It may have an underscore within it as well. It could just be the same thing. Either way. Look for that phone. Great. And I could even add a color for that if needed. Maybe I just wanna keep these all the same. Phone call, we'll call it a meeting. And for this, do we have a meeting icon? Right? We calendar looks nice. Maybe we want to track demos separate from meetings. Alright. So we drag a demo. That was the TV icon that we had. Great. What else are we gonna need? Like, an email? Not sure I would Yeah. Maybe, like, a follow-up email. We wanna reschedule that. And then we'll do an email. Great. Demo, phone call. I think I'm gonna set this to be underscore value. Cool. Alright. Looking good. Activity type. Okay. What else are we going to need for this? We need a due date for this activity and the status, we can use as as is. It's published draft, archived. What do I really care about this activity? It probably isn't completed or not. Right? So maybe we scrap status, and maybe we just go for a toggle instead. Right? Hey. Is this completed? The default value, great. For our label, maybe we change it to completed and give it a color of color on. Color off is red, just to show that it has not been completed. Great. And let's just clean up this form a little bit, making sure everything looks nice. Okay. We are going to add a date for this, and let's set a specific date and time that this thing occurred. Call it due date, end date. Due date seems reasonable, but when do we actually need to complete this specific task? Okay. So now we've got an activity. We want to, link this to our actual deal so we can track those. Right? So, what I'm gonna do in this case is create a mini to one relationship because this activity could only belong to a single deal. Right? We're gonna use the key of deal. The related collection, we'll set that to deals. We'll show the name of that deal. Maybe we show the actual organization as well so I can actually dig into the related collections and and show values from that? Excuse me one second. Sorry about that. Cool. Alright. So now, what I forgot to do is create that inverse relationship. You can actually set that up via direct us when you're creating that relationship. But now I can also just go into our deals, not deal. We'll go into our deals, and now we're gonna create a one to many relationship back to those activities. So we'll call this key of activities. We've got activities. The foreign key will be deal. That already exists. Maybe we wanna show these in a table. We'll choose the columns, due date, Name, Due Date, Activity Type. Seems pretty savvy. And I can even filter these, right? So if I wanted to see just activities where they were not completed. We can enable search and filtering and show a link to these. We'll take a look at what all these look like in just a moment. But we've forgotten one important step through this whole process is, hey. We need somebody to assign these deals to. Right? So, let's add a many to one relationship. We'll call that the deal owner or, you know, you potentially say who this is assigned to. Like, the deal owners, again, kind of standard naming in these scenarios. And for the related collection here, we're gonna use directus underscore users. So these are gonna be actual users of the application that we're assigning this to. Invalid payload collections can't start with direct us users. Oh, deal owner. Let's go to our related collection, and let's get direct us underscore users. And, in this case, we're gonna show the first name, last name. We may back up and do an avatar as well. So just the the thumbnail, the avatar, I could move these around just by using edit raw value. You'll see these are just, the standard mustache syntax that you see throughout Directus as well. Excuse me. Let me get this a drink of water. I'm actually dying. That's a turn of fate. Okay. Alright. So we're gonna save this. Deal owner already exists in deals. Okay. Alright. Great. Let's move this around. Maybe we make deal stage half width. We slide deal owner up there. Let's actually take a look at this now. I should be able to assign folks. So let's create a new user. We'll just call it sales rep. Salesrep@example.com. And maybe we give them a nice avatar. Right? Sales rep, avatar. Let's just see what Google comes back with. John's inside sales rep. Yeah. This looks this is perfect. This is my guy right here. Alright. There's his avatar. We'll just save that. And now we can see who we've assigned this particular deal to. And now maybe within the deal card user here, I wanna show who that deal owner is. Great. Mister sales rep. Looking good. Alright. One of the other things that we need to do on our activities, we probably got a an owner of that activity or assigned to. It's been assigned to somebody on the team. Again, that's going to be assigned to a direct us user. We'll save that. And lots going on here. Just some type of weird glitch. I could see a couple of extra collections. I'll just remove these. Alright. Cool. So now weive got a deal. Weive got a table full of activities. I can go ahead and add these, like, follow-up on proposal. This is assigned to Mr. Sales Rep. We've got the activity type. We can see that's going to be, just a quick phone call. This is completed. We can see that conditional, conditional formatting for that. And we can add a due date of, let's say, the next Friday. Great. Save that. Keep editing. Cool. Alright. So, now, we've got the basic inner workings of a CRM. Right? We've got our deals, we've got contacts, we've got organizations, we've got our different pipeline stages. Right? If I wanted to organize these things a bit, we could go in and add different icons for each of these. So, you know, maybe we set some people icons for our contacts. We've got our organizations. Do we have an organization option? Let's look and see. Business. Is there a business? There you go. That looks somewhat like a business. We've got activities. Maybe this will be like a checklist. Cool. We've got our deals. Let's make those the money. Dollar signs, that's great. And then, deal stages, to me this is like a settings. Right? So I could create a new folder, let's just call it settings folder. You don't necessarily have to add this, but maybe we just do to keep it clean. And we look for, like, a settings icon just to use here. That's great. This one looks magical. Settings suggest. Right? And, again, I can change the name of this to just say settings. So, it still creates this collection, but, we can call it whatever we want. So we'll drag this up within settings folder. We'll drag deal stages and, this will be, what, like the Kanban view. There we go. Awesome. Okay. So now we get a little more organization to this. One of the other things that you might do and, you know, that you use all the time within ACRM are saved views. Right? So Directus gives you that ability with bookmarks. We'll just go in to the top here and maybe I want to sort by a specific sales rep. Right? Like, the deal owner is, a specific person and specific name is sales rep. That's the only one at this point. But I could go in and create a bookmark for this, and we could call it, deals sales rep Man. And we can change this up, give it a color. Now, within that collection, now we can see we've got our deals for Sales Rep Van, and I could save that bookmark. So even if I go into the main deals view, and maybe we change this back to a table view, which could be easier for, you know, maybe a sales manager or something who's controlling this. So I had deal owner back to this as well. Now if I go back, deal sales rep man view, boom, there it is. It's saved. I could go in and update this if I wanted to as well. So now we've got our pipeline. When we go into each one of the deal, we have our name, we've got our organization, we've got the notes, we've got our activities. You know, we can mark these activities off as completed. That seems like a great CRM baseline. Let's let's take a look at where we're at. Right? We got, like, 16 minutes on the clock. This feels like a win. I don't I don't know if we wanna run that one, let's discuss where we could go from here, right, maybe we want to automatically send some emails when it hits a certain stage in the pipeline. So let's just call these things done. Right? We can manage all of our contacts. We can manage all of our organizations. We can manage our sales pipelines. We can track our activities and follow-up. So let's say, you know, we get a new deal inbound. Maybe we want to automatically assign that to a a particular person, or we want to send a notification to our sales rep when that assignment happens. Let's figure out how to do that. Right? So I'm gonna go in. Let's just create a new deal. We'll say actually, let's wait a moment. Let's go into our flows. This is a good example. Right? Whenever a a new deal comes inbound, we want to send an email notification to our sales rep to to let them know. Alright. So we're gonna create a new flow. We'll just call it new inbound deal. Pretty straightforward. We could change this to the new symbol if we want to and just do a trigger setup. So what are we going to choose here? Directus gives you a ton of different options, as far as what to use when you're creating a flow. In this case, we're going to use the event hook. So when a certain event happens inside the platform, we wanna trigger an automation. The type that we're gonna choose here is action non blocking because we don't, the the filter allows you to basically either adjust the payload when a new deal gets created or a new event happens. Action non blocking, again, that runs after a create or update action. So in this case, let's do the items dot create. Whenever we're gonna trigger this based on the deals collection. And cool. Alright, so now I'm just going to save this, right? I'm going to go in and let's create a new deal inside the system. It's in the new stage. We're gonna assign this to mister sales rep. New deal automation. And we add, let's set this one to be for the little league. Again, we choose a specific contact, add some notes, and, maybe, the deal value, we'll just ballpark it at $5,000. Great. If I go back, now I can see that in my logs, I've got this flow. Here's the payload of this particular flow, and we could see who the deal owner is on this particular deal. How do we send an email notification to that specific owner? Right? I'm just gonna take this, copy it, and I open up just my Versus code editor where I've got my 100 apps, just, Docker Compose file here set up to run this locally. I'm just going to save that in case I need it. And, next, let's flesh this out a little bit. Right? So you can see the data we're getting back here. We're probably gonna need to look up that specific user. We can find them there. Those are the deal owner. We could send them a notification inside the app or we could send them a notification via email. Right? There's 2 options there. We've got notification. In this case, we've got the UUID of the user that we're going to send that to. So, you know, potentially, you want to send that in app. App. In this case, if there's a new deal, they're probably gonna be in their inbox. We're going to send that new deal to them. But let's actually find that email address first, though. Alright. So we're gonna read data from a specific collection. Let's call it Find User is the actual step we're gonna do here. For the permissions, let's just give full access. And, for the collection, you can see I don't have the Directus Users collection here, but I can go in and edit my raw value and just use Directus underscore users. And for our IDs, right, what are we gonna put here? So if I open this back up, we're gonna use the trigger. Payload. Dealowner. So we'll do this, we'll do trigger dot payload dot deal underscore owner. And I'm gonna wrap this in mustache syntax. And let's try this again. So we'll read the user. Let's go ahead and maybe just add this send email as well. So we'll send the email, and this is gonna be the read underscore user. We're using the key that we set of the previous operation within that flow. Readuser. Email should be. We'll input that. New deal assigned to you. And hey. Read_user.firstname. We've assigned a new deal to you. And then I can even go through and add those different variables if I wanted to for things like the deal name or the deal ID and and add a link back to that. So let's just try trigger. Payload.deal_name. Great. We'll save it. And now let's just test this out. I'm not actually sure if I've got emails set up here locally on this particular instance though, so that could be fun. But we should be able to actually see if this runs. In light of that bit of news, because it just looking at my configuration here, I do not have email configured here locally. So let's let's detach that one, and let's just test the notifications. Send notification. Cool. Send notification. The find_user.id. Cool. Full access. And it was a new deal assigned. We'll just add the key here. So that'll be trigger dot payload. Or no. Actually, it may be something like this where we have key. Alright. Let's just take a look just to make sure. Alright. Within our payload, we can see the key there. That's good. In that case, we probably didn't need to get the actual user there, but that's okay. I'm just gonna copy this message that we set here. Just paste that. And let's take a look at where this gets us. Alright. So we've got a new inbound deal. We're going to read the data of the user that we've assigned that to and we're going to send a notification to that specific person. Alright. Now we've got a new deal, deal stage. Let's just set it to new. Deal owner. I'm gonna choose myself here so we could test this notification. Say deal name. Test deal. It's worth $6,000,000. Very nice deal. We got a primary contact. Here's some notes. We'll just save that and let's see what happens. Right? We go to our Flow, we get a new inbound deal, we can see our logs, send notification, recipient is so and so. Hey, undefined. We've sent you a new test deal. Underscore first name. Why did that not come back? Read underscore oh, that's why I used the wrong key. Sometimes that happens. Find user dot first name. Great. So now, if I look and I check my activity log, I can see that I've got this new deal signed. Here's the notification for that. And if I click on it where it says view content, it should take me to that specific test deal. Great. Awesome. Let's call that a win. Right? We've got our custom CRM built out. We've set up some automation for this. We could go further and flesh this out a a ton of different ways. Right? If we talk about it, like, we could go through and send emails to our actual primary contact when it reaches a certain stage, that would be fairly easy to do using direct dis flows. You know, we could maybe even go as far as, like, a future future iteration of this could be setting up an inbox to parse incoming emails like a BCC functionality and add these to those specific deals as well. But this this feels really good. I'm I'm calling this a win. That's our custom CRM. Thanks for joining me on this episode of 100 apps, 100 hours. We'll catch you on the next one.","592c22ad-1cb2-4d65-9bd7-e0d7c98fe4f2",[185],"e9e66fa8-0650-4e37-ae8b-74755fdd5dca",[],{"reps":188},[189,245],{"name":190,"sdr":8,"link":191,"countries":192,"states":194},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[193],"United States",[195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244],"Michigan","Indiana","Ohio","West Virginia","Kentucky","Virginia","Tennessee","North Carolina","South Carolina","Georgia","Florida","Alabama","Mississippi","New York","MI","IN","OH","WV","KY","VA","TN","NC","SC","GA","FL","AL","MS","NY","Connecticut","CT","Delaware","DE","Maine","ME","Maryland","MD","Massachusetts","MA","New Hampshire","NH","New Jersey","NJ","Pennsylvania","PA","Rhode Island","RI","Vermont","VT","Washington DC","DC",{"name":246,"link":247,"countries":248},"Michelle Riber","https://meetings.hubspot.com/mriber",[249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387,388,389,390,391,392,393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,226,437,438],"Albania","ALB","Algeria","DZA","Andorra","AND","Angola","AGO","Austria","AUT","Belgium","BEL","Benin","BEN","Bosnia and Herzegovina","BIH","Botswana","BWA","Bulgaria","BGR","Burkina Faso","BFA","Burundi","BDI","Cameroon","CMR","Cape Verde","CPV","Central African Republic","CAF","Chad","TCD","Comoros","COM","Côte d'Ivoire","CIV","Croatia","HRV","Czech Republic","CZE","Democratic Republic of Congo","COD","Denmark","DNK","Djibouti","DJI","Egypt","EGY","Equatorial Guinea","GNQ","Eritrea","ERI","Estonia","EST","Eswatini","SWZ","Ethiopia","ETH","Finland","FIN","France","FRA","Gabon","GAB","Gambia","GMB","Ghana","GHA","Greece","GRC","Guinea","GIN","Guinea-Bissau","GNB","Hungary","HUN","Iceland","ISL","Ireland","IRL","Italy","ITA","Kenya","KEN","Latvia","LVA","Lesotho","LSO","Liberia","LBR","Libya","LBY","Liechtenstein","LIE","Lithuania","LTU","Luxembourg","LUX","Madagascar","MDG","Malawi","MWI","Mali","MLI","Malta","MLT","Mauritania","MRT","Mauritius","MUS","Moldova","MDA","Monaco","MCO","Montenegro","MNE","Morocco","MAR","Mozambique","MOZ","Namibia","NAM","Niger","NER","Nigeria","NGA","North Macedonia","MKD","Norway","NOR","Poland","POL","Portugal","PRT","Republic of Congo","COG","Romania","ROU","Rwanda","RWA","San Marino","SMR","São Tomé and Príncipe","STP","Senegal","SEN","Serbia","SRB","Seychelles","SYC","Sierra Leone","SLE","Slovakia","SVK","Slovenia","SVN","Somalia","SOM","South Africa","ZAF","South Sudan","SSD","Spain","ESP","Sudan","SDN","Sweden","SWE","Tanzania","TZA","Togo","TGO","Tunisia","TUN","Uganda","UGA","United Kingdom","GBR","Vatican City","VAT","Zambia","ZMB","Zimbabwe","ZWE","UK","Germany","Netherlands","Switzerland","CH","NL",1773850435165]