[{"data":1,"prerenderedAt":438},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-remote-job-board":121,"100-apps-100-hours-remote-job-board-next":169,"sales-reps":186},{"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":8,"episode_number":128,"published":129,"title":130,"video_transcript_html":131,"video_transcript_text":132,"content":8,"status":133,"episode_people":134,"recommendations":149,"season":150,"seo":167},"6bff0c09-ad87-4d5c-b227-89b8c3c02220","remote-job-board","936433174","Race right along with Bryant as he tackles building a virtual job board inspired by We Work Remotely. He tries to build a ton of functionality in just 60 mins – job listings, integrating categories and companies, and job post submissions with Directus and Nuxt.","c9c86637-29d7-4b38-b798-2a5a52ac2def",66,5,"2024-05-17","Mission: Remote Job Board","\u003Cp>Speaker 0: Hi. Welcome back to another episode of 100 Apps, 100 Hours. I'm your host Brian Gillespie and on the show we build some of your favorite apps or some of your suggestions or just random stuff that I've come across on the Internet in 1 hour or less or get publicly shamed trying when we fail. And I fail often. That's just the rub of this format.\u003C/p>\u003Cp>The rules are there are 60 minutes to plan and build the application, no more, no less. Sweating just thinking about it. But rule number 2 is use whatever you have at your disposal. AI, GitHub Copilot, tailwind CSS, tailwind UI, any pre made components, whatever we can to get the job done in 1 hour. Today, we are going to be building a job board.\u003C/p>\u003Cp>So the giant elephant in the room in this space is obviously Indeed. They are one of the largest job boards. Yeah. It's been a while since I've been on their site. The experience is not great, honestly.\u003C/p>\u003Cp>Maybe everybody's working on a a mobile or something, but, I I think the the job board that we're gonna model after today is we work remotely. We are an all remote team here at Directus, so this speaks to my heart. I've been working remotely for, the better part of a decade now. So, we are going to model after this. Let's dive in and get started, shall we?\u003C/p>\u003Cp>Alright. So we'll put 60 minutes on the clock. Let's just talk through the functionality that we're looking for here. And As I pull up the WeWork Remotely job board, what's our functionality? Right?\u003C/p>\u003Cp>We want to, obviously show a list of jobs. In this case they are sorted by category. We want to let someone let users post a job for a fee. That's nice. And, you know, this one's got subscribers as well, which seems like something that is relatively simple.\u003C/p>\u003Cp>There's a lot of moving pieces to that. So let's add a question mark and we'll do subscribe to job posts. Now I think this one does not allow you to apply within the platform. I think it just, like, sends you off to a third party thing. So that's the way we'll play this, you know, if you wanted to build like an application processing system, you could totally do that with Directus.\u003C/p>\u003Cp>That's a lot of ground to cover in 60 minutes though. So let's take a look at this, let's flesh out our actual data model, right? That's part of the planning process, so I'm just going to drag some boxes over here inside Figma. What are we going to have? We're going to have job posts or we could call it job listings.\u003C/p>\u003Cp>That's great. Within that, we're gonna have a few different things. Alright. What do we got? We got some tags, it looks like.\u003C/p>\u003Cp>So we got a category. We got a title for that post. We've got some content, we've got a link, we've got a like, a company. I guess if you're going to have a company, that would probably be here. That'd be a separate table.\u003C/p>\u003Cp>Right? There's a company. Company has a name, a website. Name. Website.\u003C/p>\u003Cp>What? Logo. Great. K. What else are we gonna have?\u003C/p>\u003Cp>If we make it 2 subscribers, we'll worry about that when we come across it, but let's dive in. Right? This seems pretty good. We'll just draw some arrows to make it look fancy. Right?\u003C/p>\u003Cp>A company has multiple job listings. One listing has one company. Alright. Great. Let's talk about what we're working with.\u003C/p>\u003Cp>So as far as applications, I've got a direct assistance spun up, it is blank. I've got a bare bones Nuxt app that has some login and register capability. Looks like I'm already logged in though, so all good there. And as far as the Directus communication, I've already got a Nuxt plug in defined just to talk to Directus a little bit easier, make it a little less boring on this specific episode. Alright.\u003C/p>\u003Cp>So let's start by I'm going to pull this up side by side with remote. Oh, no. That's not what we want. I could spend the whole time just messing with the Arc Browser. Alright.\u003C/p>\u003Cp>So we're talking about job listings. We're going to go ahead and add that as a new collection. And I think I can zoom in a little bit to make that more visible. And then we'll look at the actual listings that we have. Right?\u003C/p>\u003Cp>One of the other things that I do often is, I'm not sure how this site is put together, but, you know, on sites that are server side rendered, you can go into the actual code and especially if it's, like, a Next application or, I'm not sure if Nuxt does this or not, but you can actually see the data that's posted. I'm not sure if that's the case here. I see Jquery. This is probably server rendered from an actual server. No big deal.\u003C/p>\u003Cp>But, just one way you can kind of see what other folks are doing on the website, always inspect the code. I live in the JavaScript developer tools console. Alright, so we're going to have a job listing. We're going to have a pretty slug for all of these. So let's see what we're going to do, we're going to have a draft and publish listings, probably got a date created, did the user created, the date updated, etcetera.\u003C/p>\u003Cp>And let's go through and model out our listing, right. So we've got a title for the listing, we're going to have that. We've got to have a slug for the listing. So we'll just do a slug for the listing. We'll come back to that one in a moment.\u003C/p>\u003Cp>What else are we going to add? We're going to have some content. I see there's tags here. I don't know exactly how they're using the tags, but it looks like the the tags get their own page. So in that case, it makes me think we need, like, a relationship there.\u003C/p>\u003Cp>Let's look at that. We'll do a mini to mini relationship because one post could have many tags and there could be many tags to other posts. Alright, So we got tags as a key. I'm going to enter tags as a related collection here, right? And if I open up the advanced field mode inside Directus and I scroll down to the bottom, you'll see that, it's actually going to create a new table for me.\u003C/p>\u003Cp>So that tags table that I don't have, it's going to create it for me, which is kind of nice, right? And I can add this to the tags, I can show the job listings on the tags that have that specific tag. And maybe we can sort these as well. Cool. Alright.\u003C/p>\u003Cp>So we'll hit save. We've got tags. We also should have a tags collection now, which is nice. We have a junction table for those as well. Then we're gonna need some content.\u003C/p>\u003Cp>Right? We need the actual, content for this. That's gonna be the WYSIWYG editor. Is that great? Cool.\u003C/p>\u003Cp>The type will be text, that's what it's going to be saved as in the database. I can always adjust my toolbar if I need to. And, what else do we need really? Apply for this position, right? This is going to be a URL.\u003C/p>\u003Cp>So apply URL, Application URL. That's what we'll roll with. We're probably going to need a category for this as well. So if I go back to all the jobs, we can see we've got sales and marketing. If I zoom out a little bit, we can see all the different categories up here at the top, Programming, Design, etcetera.\u003C/p>\u003Cp>So we're going to create a new relationship inside Directus. Let's call it category. And we will again create a new collection here. So if, even if this doesn't exist, Directus is going to create this for me, which is, you know, super. It's a really easy, super nice way to do our data modeling.\u003C/p>\u003Cp>We can add the different job listings to the categories if we want to. In this case, maybe I don't need to show that. But we will, we don't have anything to adjust as far as the interface because this is not created yet, but we'll just go through the process. So we've got a category for this, We've got tags. And if I go back to my data, I'll see categories here now.\u003C/p>\u003Cp>So we've got categories, we've got tags. Great. What else are we missing on this? Is this a featured post or not? I'm assuming that's part of the checkout process.\u003C/p>\u003Cp>So, you know, we could add a Boolean tag for that is featured. I always like when I'm doing my keys, especially if they're Boolean prefix with the is. There's just a nice little standard naming convention. And on my interface, maybe the label is featured post. Great.\u003C/p>\u003Cp>Okay. Alright. So this looks pretty good, I want to say. Alright. We'll make some of these things half width.\u003C/p>\u003Cp>Maybe we make the title full width. We got the status. Actually, let's do this. We got status, we got title. Let's put slug down below title.\u003C/p>\u003Cp>And don't sleep on the Directus marketplace. Right? One of the extensions that I've been using a lot here lately is the WP slug interface extension. If you're used to WordPress, you got, like, this nice little slug interface there. We're just gonna install this, give it a quick refresh.\u003C/p>\u003Cp>Then I go back to my data model, I go to my slug, go to the interface, and I can switch this up to my slug. I get a template for this, so we're going to populate the title. I could potentially prefix this or, you know, auto generate it as well. So I'm just going to do On Create because after a thing is created, it's been published, changing the slug results in a different URL on your front end, could result in some nasty 404 errors. We don't want that.\u003C/p>\u003Cp>40 fours get out of here. Alright. So we got a job listing category. We've got did I\u003C/p>\u003Cp>Speaker 1: do job listing instead of job listings?\u003C/p>\u003Cp>Speaker 0: I did. Man. Alright. How can we fix that? We're not going to bother with it.\u003C/p>\u003Cp>I could dig into the SQL and adjust that if I needed to, but we're not going to worry too much with it right now. Typically, I keep my table names plural. Just a personal preference. Alright. So we've got a category.\u003C/p>\u003Cp>Let's go ahead and flesh out these categories. We need a title for the category. We probably need a slug for the category as well. So I can go back into my job listings, and if I want, I can duplicate this field to that collection. So we'll just change this to slug.\u003C/p>\u003Cp>We'll go to categories. Great. And in that case, do we actually want the tags to have their own page as well? The tag will have a title and the tag will probably have a slug as well. So we'll just copy that across.\u003C/p>\u003Cp>Slug. Change the collection to tags. And away we go. Right? The last thing that we talked about is a company.\u003C/p>\u003Cp>So we want to be able to select a company for our post and you know potentially a company could post 3 or 4 different times. Maybe they're hiring for a lot of different positions. So let's create a new table. We're going to call it companies. We'll do a generated UUID.\u003C/p>\u003Cp>Do we need all this information? Probably not. Alright. So we'll give the company name. And whatever I choose to name this key is totally up to me.\u003C/p>\u003Cp>And then we've got the website. That's just going to be a string. And then we're going to have a logo for that. Right? Logo.\u003C/p>\u003Cp>Alright. Cool. So now we've got it's just a basic data model set up. Let's go in and I'm just going to, like, just rip some of this content. We work remotely.\u003C/p>\u003Cp>Sorry for just sealing information off your job board here. Let's look at our different categories. So we'll just mop up a couple of these categories really quickly. We've got what? Programming?\u003C/p>\u003Cp>Oh, gosh, I can't spell. Programming, save. We've got Design, save. What else do we have? Sales and marketing.\u003C/p>\u003Cp>Sales and marketing. And what happens if we do this? Sales and? Oh, that's kind of a nice effect. Sales and marketing.\u003C/p>\u003Cp>Maybe we change this to just be sales dash marketing. Customer support. Customer support. Great. Okay.\u003C/p>\u003Cp>And this is all remote, so, you know, what are we gonna call this thing? Maybe we'll call it it, Dan's Remote Job Board after some of our own teammates here at Directus. Alright. So we've got some categories. Let's take a look at the tags within here.\u003C/p>\u003Cp>We've got full time. We've got, part time will be a tag. Right? Part time. What?\u003C/p>\u003Cp>SEO? Link building, WordPress, technical writing. What do we have on the development side? So we got programming. We got full stack, front end.\u003C/p>\u003Cp>What kind of tags do we have here? Europe only, etcetera. Hang on just a moment. I got an important call coming in. Be right Alright.\u003C/p>\u003Cp>So now we're back. We've got 46 minutes on the clock. Let's dive in further. We've got some tags. We've got like Europe only, etcetera.\u003C/p>\u003Cp>Great. Okay. So, those are a few tags. We've got some categories. We've got, let's see if there's any companies that we recognize on here for full stack development.\u003C/p>\u003Cp>I don't really recognize any of these. Right? Let's just create a new company here. We'll call this Directus HTTP Directus dot io. And we'll add the logo for this.\u003C/p>\u003Cp>Right? See what I got handy. Directus logo. We'll get this a couple of different ways. Logotype.\u003C/p>\u003Cp>That looks good. That's what we will roll with. We got the logotype. Maybe we add another company, but for now let's just add our first listing. Right.\u003C/p>\u003Cp>I'm just going to steal this one, full stack. Alright. We are going to make sure this is published. Is this a featured post? Looks like it is.\u003C/p>\u003Cp>Alright. We'll go in. And I could just copy this wholesale. And I'm thinking it should retain some of the content, some of the settings. Yeah.\u003C/p>\u003Cp>Okay. So it should retain the formatting for it. And then we're going to have a link for the application. And the last thing that we need to do is pick a category. And we probably need to add a company for this as well.\u003C/p>\u003Cp>And I I joked, it's not the last thing we need to do. We're going to add a few more. This is going to be a Europe only job. Be a $100,000 or more USD. That's that's kind of a nice tag.\u003C/p>\u003Cp>Let's do React as a tag. Oh, sorry. Creating an item in a tag. React. Great.\u003C/p>\u003Cp>Alright. Is this a featured post? Yes, it is. Let's save that post and then we'll go in and add a relationship to that company. So that'll be a many to one relationship inside Directus.\u003C/p>\u003Cp>We're gonna call this company because there's a single company, and then we have companies. And, you know, we could go in and add the company name here so that it displays nicely. We probably want to do the same thing for our categories, like give them a title, make sure those show on the interface in the display template. Same thing for tags. For our list view here, we're going to do the tags ID dot title and we'll do the same thing here.\u003C/p>\u003Cp>Great. Okay. So now if I open up this job, we can see our different tags for this. We've got our category. We've got the company that this is created for.\u003C/p>\u003Cp>Sweet. Alright. So now we've got a job listing. We've got some categories, we've got some tags, we've got some companies. Does a company going to have a slug?\u003C/p>\u003Cp>You know, potentially. Do they have a listing here? They do. So probably need a slug for the company as well. Slug.\u003C/p>\u003Cp>The template is going to be the name of the company. Great. Alright. So we've got our data model for this, right? The next thing that I'm going to do is just go through our access control and create this.\u003C/p>\u003Cp>So there are 2 roles that are out of the box inside Directus. I have public, I have administrator. I'm going to go through and, just basically set read access to all these. And then I can go back in and adjust this if I need to as well. So we're going to adjust the item permissions just a little bit to where we can only see published posts.\u003C/p>\u003Cp>So I'll go through listing tags, categories. I don't know the categories. Do we do publish for categories? We did not. Tags, etcetera.\u003C/p>\u003Cp>Yeah. So the rest of those are fine as is. Right? People can see all the companies, all the tags, all the categories. But if it's not a published listing, they can't see that information.\u003C/p>\u003Cp>Alright. So great. Let's go in and now, is it time to actually start fleshing something out inside our Nuxt application? Let's let's take a look at that. Right?\u003C/p>\u003Cp>So we've got here on the how do I separate these 2? Alright. Cool. So, Nuxt application on our pages directory, we've got an index page. This is probably going to be just our listing of posts.\u003C/p>\u003Cp>Right? So we can start fleshing this out a little bit. Let's remove this centering. That should move it way up here to the top. We'll just add a header for this.\u003C/p>\u003Cp>And what are we going to have in the header? We're going to flex. We are going to do Dan's remote job board. That's the name of this thing. We'll make sure it's like a mono font for Dan.\u003C/p>\u003Cp>I know how Dan likes this stuff. Okay. Let's give this header a little padding. PX6 PY. Alright.\u003C/p>\u003Cp>And we can even make this like sticky at the top if we want to. Sticky, top 0. Give it a background white. BG opacity. We do that backdrop blur effect.\u003C/p>\u003Cp>One of the nice things about using tailwind, backdrop blur, BG opacity. I could actually just adjust it this way. Let's do dash or slash 50. And cool. Alright.\u003C/p>\u003Cp>So now we've got a header. We might have some links inside our header that we'll cover in just a moment. Let's do a div to ul. Links. Do we actually need these on a list?\u003C/p>\u003Cp>Probably not. Let's do what? Nuxt link v 4, links and links, links on links on links. Let's actually call this nav links. And we'll just flesh a few of these out real quick.\u003C/p>\u003Cp>Nav links, that'll be an array equals array. To, about, contact, no. That's we want none of that. Home, we got what? Categories.\u003C/p>\u003Cp>We'll come back to it. Let's just nuke that for now. Or we could just comment it out. Alright. As far as what we want inside the actual body of this here, we'll do this in the main section, we are going to fetch our job listings, right.\u003C/p>\u003Cp>So Nuxt has a couple of nice composables for fetching data. We're going to use that here. But the first thing I'm going to do, let's import the read items operation from my Directus SDK. Nope. Directus SDK.\u003C/p>\u003Cp>And then I'm going to use the async data call, the async data composable from Nuxt. Use async data, And this handles like caching and deduplication, and actually if this is server rendered it will automatically dedupe those calls and pass that data from the server to the client so that I don't have to fetch the data twice and hit that API. So we'll give this a, let's say this is listings index. We'll give it a key and then we'll actually fire this off. And we're going to actually await this data.\u003C/p>\u003Cp>Alright. Then within here, we're actually going to use our Directus client that we've set up. We're gonna access that through our Nuxt plug in and we are going to do this. So we'll say return Directus dot request. We'll do read items.\u003C/p>\u003Cp>We're gonna read from the what do we call it, actually? Call it the Job Listing Collection. So that's going to trip me up this whole time. And then we have a query that we could set up. We could run this where it says filter status equals published, but I'm handling that here within my access control inside Directus, so it doesn't really matter.\u003C/p>\u003Cp>So as long as this is published, like if I go in and let's just say I change this. Change this. We say we maybe set this to draft, and I hit save as copy. And I probably need a new slug for this. And I would definitely make slug unique here, so let's just do that quickly before I forget.\u003C/p>\u003Cp>We'll go into the data model, we'll go to listings, we'll make sure that slug has to be a unique value. Alright. So this will be listings and then I can I typically do something like this whenever I'm building just to quickly and easily make sure I'm getting all the data that I want back from my API? Alright. So we go to local host.\u003C/p>\u003Cp>We can see we've got this job board and I need to actually get this out of the header and into the main body here. Alright. So there's our application, our job listing, basically. Alright. That's great.\u003C/p>\u003Cp>We can see our tags, we can see the company. We can't really see any of the information about them, though. Right? And if we take a look at our postings here, let's see what we've got. We've got the company information, we've got the company name, etcetera.\u003C/p>\u003Cp>So how do we go and fetch that data with inside Directus? We'll just go in here. We'll do something like this where we have fields. I don't recommend doing it this way in production, just using these wildcards. But for something like this where I'm quickly prototyping, super handy for that.\u003C/p>\u003Cp>I could just get all of the root level fields, I can get all of the company level fields, I could get the category, category dot star. And then for our tags, I'm going to get tags dot tags underscore ID. I'm going to get that field. So if I do that now, you can see I get the extra information that we're looking for. So I've expanded my relationships for tags, for the company, for our category, basically everything I need here.\u003C/p>\u003Cp>Looks great. Looks great. Okay. And I'm getting both of those things here because I am using session authentication. I've got a cookie here that's storing my session token.\u003C/p>\u003Cp>So if I wanted to, I could go into incognito window and that should solve it where I'm only getting one of those items back. Alright. So I'm gonna pull this up. We work remotely over here on the side just so I can see that. And we'll start fleshing this thing out.\u003C/p>\u003Cp>So within our listings, we've got, let's do a div. Probably add, like, a h two here. This is our job listings. Get that font bold. Text XL.\u003C/p>\u003Cp>Maybe 2 XL. Alright. And maybe we give the whole main section a little bit of padding. I I think there's a U container Nuxt UI component that we'll just use for this. I'm using uidot nuxt.com, just a Nuxt UI library, just to have something to build with quickly.\u003C/p>\u003Cp>Alright. So we've got our job listings. Let's go through we're gonna do a card component for each one of those listings. So v 4 job in listings. The key, we can use the job dot ID.\u003C/p>\u003Cp>And here, inside the card itself, let's see what we got. Job title. Is this actually gonna be what we want? What's this gonna look like? Remote.\u003C/p>\u003Cp>It's not really anything that we we actually want here. This is gonna be company name, category dot name, Category dot title. And and this could be remedied by TypeScript if I had all these things typed probably. But as far as the tags, that's going to be the title of the tags as well. Cool.\u003C/p>\u003Cp>Alright. So how can we get this to look a little more like what we have? We'll go through and flesh this out a bit. Wrap this. We'll put it in a, it doesn't even need to be a grid.\u003C/p>\u003Cp>We could just do like a single grid. Give it some gap. 6. Alright. We got some space there.\u003C/p>\u003Cp>We'll give it a little breathing room. Cool. Alright. So we've got some job listings. We're going to want to give these a link.\u003C/p>\u003Cp>Where did where did that go? Oh, just disappeared. Need to learn my keyboard shortcuts, don't I? What are we what are we doing, Brian? That did it again.\u003C/p>\u003Cp>Alright. Maybe I'll just use the cut paste here. Wrap this in a Nuxt link component. We're gonna do this, the 2 for this will be something like this, where we have listing, plus job ID. Yeah.\u003C/p>\u003Cp>Potentially let's actually do it this way, just use a template literal. Job dot slug. Wrap that, don't forget the ending tick. Cool. And now we have a link that does not work because we don't have a route for that.\u003C/p>\u003Cp>But let's see if we could show the company information as well. Maybe we put that here. It would be within a div. We got the company name. Show Nuxt image source dot company logo.\u003C/p>\u003Cp>See if we can get that displaying. Okay. Now we'll shrink that a bit. Object Oh, hang on a moment. Just go here.\u003C/p>\u003Cp>Class, object contain, make it 24 high, 24 wide. Cool. Within this, we could do, like, a flex, add a little bit of gap. And, you know, if we wanted to, we could get fancy with it. But let's, how do we set this off a little bit?\u003C/p>\u003Cp>Oh, actually, the title for the company is over here on this side in it. Alright. So go in, wrap these in a div. That should give us like some type of formatting here. And if I really wanted to make it look like the one on WeWork remotely, and I don't, we'll just add a border for this, round it a little bit, get a little bit of padding, p 1.\u003C/p>\u003Cp>And maybe we make the border pretty. Make it violet. There we go. That looks poor, actually. I don't like it.\u003C/p>\u003Cp>Alright, we'll make it more subtle. There we go. So we got Directus, we got Change This as the name. We've got our job listings. How are we doing on time?\u003C/p>\u003Cp>Time wise, we're at about half an hour, right? Alright, so we've got some\u003C/p>\u003Cp>Speaker 1: job listings. We\u003C/p>\u003Cp>Speaker 0: can see a list of those. How would we do something like a filter for these? Or we'd probably actually want to list these by categories. Right? So we'd probably actually switch up our data fetching here into, we'd probably look for categories first.\u003C/p>\u003Cp>But instead of diving into that specifically right now, let's actually get a detail page up. So we've got a route for this index page. Let's go in and add a route for our listings. Great. And then we'll go in and add a new page.\u003C/p>\u003Cp>Let's call this the slug. View. So we do have a listing there. And maybe I just copy pretty much all of this. We'll actually, what do we wanna keep here?\u003C/p>\u003Cp>Let's just keep the card component. That's got our information. Maybe we keep the u container just so we get some base styling. And if I wanted to, I can move this header component out of this and into our actual app itself or in probably like the default layout would be a good one as well. Just post that there.\u003C/p>\u003Cp>So now that will show on every single page. Alright. So as far as our job listing, we're going to fetch that data here. I'm still going to use the read items call, because we're fetching by the slug and not the actual ID. So what we're going to do inside Knox would be something like this, where we use the route composable, or use route composable to get the route.\u003C/p>\u003Cp>And then we're going to set\u003C/p>\u003Cp>Speaker 1: up a filter for\u003C/p>\u003Cp>Speaker 0: our slug, and we want that to be equal to route. Params.slug. So this should give us our listing. And then what we're going to do here is at the end of this, using the use async data and the I think it's use fetch composable inside Nuxt have an option set where I can transform this data when I return it. So this is going to give me an array and I only want the first item of the array.\u003C/p>\u003Cp>So even though this only matches one thing because the slug is unique, it still returns an array because of the read items SDK, or the read items method of the SDK. So we're just gonna transform that. And then here inside our card, we don't really need that anymore. Right? So this gives us what we want.\u003C/p>\u003Cp>Cannot read properties of undefined of slug. Why do we not have this? Right? Here's our listings. There's our slug.\u003C/p>\u003Cp>Route.params.slug. Directus.requestreaditemsjoblisting. Cannot read properties of undefined of slug. Let's just comment this out, see if the page actually loads. Yes.\u003C/p>\u003Cp>It does load. Let's figure out why we're not seeing that. So, the Vue dev tools is is really a great tool if you're developing with Vue. We will look for the slug component. Let's see what we got.\u003C/p>\u003Cp>We got listings. Oh, that's why. That would make sense. Right? Sometimes it's the typos, especially when you're building quickly.\u003C/p>\u003Cp>They get you into trouble. So here, let's just remove the card. I don't even need this Nuxt link component anymore. Okay. And within our layout, let's make it let's get some navigation so we can actually move backwards here.\u003C/p>\u003Cp>Make this a Nuxt link. Nuxt link. Change it to 2, do slash. Cool. So now I could go should be able to go back and forth.\u003C/p>\u003Cp>There we go. Go here. Go there. Amidst options next to\u003C/p>\u003Cp>Speaker 1: header slot.\u003C/p>\u003Cp>Speaker 0: What am I doing wrong there? Alright. Anyway, so this is gonna be our job. And now we have got our job listing. Cannot read properties of type.\u003C/p>\u003Cp>Alright. Let's go back into Directus. And is this because of our our access control? We've got it. Let's set it up so we can read the files as well.\u003C/p>\u003Cp>Does that give us what we want? Okay. So at least now I've got some type of card information here. How do we want to display the actual content for this? If we go into one of these, we've got, the job title, the logo here is on the right as well as the company.\u003C/p>\u003Cp>So we can go in and flesh this out. You know, we could spend a ton of time on design here. I'd rather focus more on the important bits. So let's just do a main component. This is where we'll stuff the content.\u003C/p>\u003Cp>We could do v dot vhtmlordiv.vhmml, v dash html. That'll be our job dot content. Okay. That should show. Cool.\u003C/p>\u003Cp>This is not really nicely formatted. Right? So, using Tailwind, they have this really nice pros class we could use to get that formatting. Looks pretty great. Maybe we add some padding to this container.\u003C/p>\u003Cp>I don't even need that. Strip this away or not some padding. Give it some margin. This looks a little odd here at the top. Let's remove that.\u003C/p>\u003Cp>We've got Directus is the company. And this could be another NuxLink. This could be, what, to the company?\u003C/p>\u003Cp>Speaker 1: 2/company/job.company.slug.\u003C/p>\u003Cp>Speaker 0: That's what that would look like. And inside this div, maybe we just add some spacing. Okay. Alright. So now we could potentially navigate to the company, but there is no page for that.\u003C/p>\u003Cp>But here we've got our actual job posting. How do we we want to use like a button for you button. Is it the apply URL? I think I've got application URL here. Apply for\u003C/p>\u003Cp>Speaker 1: this position.\u003C/p>\u003Cp>Speaker 0: Great. What's that gonna do? Job application URL, is that what I set this up as? Let's take a look at our data model. Application URL, job application URL, that should be correct.\u003C/p>\u003Cp>And we want this to be let's make this a block class. Block. And maybe we drop that below the actual name. We don't have a description for this. It's great.\u003C/p>\u003Cp>Maybe we do this. Just set off this metadata just a little bit. Okay. We hit apply for\u003C/p>\u003Cp>Speaker 1: this position. And this\u003C/p>\u003Cp>Speaker 0: should open that in a oh, it's because I've got HRF. Let's try this. Apply for this position. That should give me this application page. Great.\u003C/p>\u003Cp>What is this going to be? In line block? There we go. Okay. So now we've got our page, we've got our company information, we've got our job description.\u003C/p>\u003Cp>We can drop another button at the bottom of this content. Looking nice. Alright. We are cruising on, like, 20 minutes worth of time. Right?\u003C/p>\u003Cp>So how can we actually create a post? Right? We've got our post. Let's go back and we'll do a new route. We'll just call it listings dot new.\u003C/p>\u003Cp>I'm just going to copy the same setup that we've got. Just moving really really quickly. Just drop everything out of here. We're gonna keep Directus. In this case we're gonna use create items.\u003C/p>\u003Cp>Create item. Great. And on the front end, if I wanted to be able to create a listing, we're going to need to add some navigation for this. So we'll just, let's actually stick this inside our header. Right.\u003C/p>\u003Cp>Where are you? That's in our default layout. So over here on the far right, we can actually flex these items, justify between. Oh, sorry. I already had the flex.\u003C/p>\u003Cp>Great. And we'll do listings dot new, post a job. Great. That takes us to the job new page. And on the new\u003C/p>\u003Cp>Speaker 1: page, why do we not okay.\u003C/p>\u003Cp>Speaker 0: Okay. So now we are going to post a job. We've got let's take our same heading here. Post this in here. We've got a create a new job post.\u003C/p>\u003Cp>Okay. Alright, so now we can start fleshing out our actual form that we want, right? So let's just do form. This could be reactive since we've got some properties here. What all do we have inside that listing?\u003C/p>\u003Cp>Right. We've got a title. You know, maybe we don't want them to actually choose the slug. We've got a category. That should just be an ID.\u003C/p>\u003Cp>We got some tags. This is gonna be an array. We got some content. That's gonna be a string. We got an application URL, and we've got a company, which is, it's actually, should be a string, just an ID, but we'll have to actually fetch that information.\u003C/p>\u003Cp>And then at the end of this, we are going to have a function to submit the form. Alright. Submit listing. And what's that function gonna look like? Oh, let's do a try catch.\u003C/p>\u003Cp>This is gonna be a constant response equals await, direct us dot request. Create item. Yes. This looks good. Form dot title.\u003C/p>\u003Cp>Okay. Alright. GitHub Copilot. I got you. And then what are we gonna do?\u003C/p>\u003Cp>Console log response. And we could send them to like a thank you page or something. Maybe we are tracking like a success state here. And we set success value to true. If there's an error, we're going to log that error.\u003C/p>\u003Cp>Great. Cool. Alright. So we got submit listing. Now we need to actually build a form.\u003C/p>\u003Cp>Right? Inside this Nuxt UI library, I think there is a, let's just dive into it, form component. You can even add validation with this using, like, Zod or something like that. But, we've got our form wrapped in a form. There's a form tag.\u003C/p>\u003Cp>We've got at submit. Let's do dot prevent. Submit listing. That's going to be our form handler. And then inside the form, each one of those gets wrapped in a form group, where we got a label equals title.\u003C/p>\u003Cp>Placeholder equals, amazing Directus developer. That'll be our placeholder\u003C/p>\u003Cp>Speaker 1: for this.\u003C/p>\u003Cp>Speaker 0: So we that's probably actually on the actual input itself. Alright. And then we have a u input form dot title. We have a placeholder. Let's see what we're getting now.\u003C/p>\u003Cp>Cool. Amazing Directus developer. Maybe our form isn't so wide. 3\u003C/p>\u003Cp>Speaker 1: x l.\u003C/p>\u003Cp>Speaker 0: Did that bring it in? It did not. Math would okay. Still a little long, but no worries. Okay.\u003C/p>\u003Cp>So we've got our title. We want to go through and add the rest of our form. We got a category. Let's see what GitHub Copilot can do here. Yeah.\u003C/p>\u003Cp>It's not giving us a lot of help here. But we can drop some of these inputs. Maybe we add some spacing between our form components here. So we got tags. We got content.\u003C/p>\u003Cp>We've got an application URL. Let's make sure we get that. Application URL. And, alright. So for this, like, the company information, the category, etcetera, This could be a list of companies.\u003C/p>\u003Cp>Right? So what do we have inside Nuxt UI for something like this? We've got a drop down. Is that what we're looking for? Does this have a search?\u003C/p>\u003Cp>No. That's gonna be input,\u003C/p>\u003Cp>Speaker 1: input menu. I'm assuming\u003C/p>\u003Cp>Speaker 0: this could be an input menu based on the preset. This is searchable. Search attributes, control the query, Async search. Okay. Here we go.\u003C/p>\u003Cp>So we got a async search function. We can search for a company inside Directus with this. This will be an async function. Let's call it search categories or search companies, maybe. Let's do the company first just to, well, actually, let's do categories.\u003C/p>\u003Cp>We have them fill out the the company information. Constant, what our data is gonna be await, direct us dot request. That's gonna be read items. We're probably gonna need, what, like, a search query as well. That'll be passed here.\u003C/p>\u003Cp>This will be our string. Loading value, read items dot categories or categories is gonna be here. Inside that request, we're gonna need a filter. Actually, let's use a search for that. And we'll just do search query.\u003C/p>\u003Cp>Now the search param here is not optimized like the filter params are, but let's see what we've got here. You form group, you input menu. Oh, wrong one. We want this to be our let's drop this in our category. You input menu, search equals search categories.\u003C/p>\u003Cp>This is gonna be form dot category. Search for a category. And we can set a loading state for this as well. Alright. Let's see what we've got here.\u003C/p>\u003Cp>Search for a category. In line properties, not length. If I do this, is it actually searching for a category? Programming? I don't see it.\u003C/p>\u003Cp>Search search categories, loading, form dot category, placeholder, option name, attribute is a title. Title. Alright. Let's see what else we got here.\u003C/p>\u003Cp>Speaker 1: I'm missing\u003C/p>\u003Cp>Speaker 0: something here. Oh, forgot to return something from that actual function. Gotta return the data, Brian. Makes sense. Alright.\u003C/p>\u003Cp>See if we got anything else now. Is this actually working? I don't see it calling any of the data, though. Direct is at request, read items, categories. Did I, oh, that's why.\u003C/p>\u003Cp>Got to import read items as well. Alright, so we refresh. Programming. Okay. Now we see sales and marketing.\u003C/p>\u003Cp>And inside my actual listing, right, where's the form? So we got our slug. This is actually new. Alright. So we got our form.\u003C/p>\u003Cp>We got the category. We need the ID of that category. Selecting the actual object. Option Attribute Title. Category dot title?\u003C/p>\u003Cp>Is that going to give us what we want? Oh, no. Option attribute by ID. Why is it not storing when it selects the value what do we actually want here? V model async search.\u003C/p>\u003Cp>Use the debounce. You know, we could add a debounce to it. Let's actually deal with that in a moment. We need to actually submit this form first. Alright.\u003C/p>\u003Cp>For the input menu, what else do we have? Okay. I see like a select menu here that is probably very similar to this. Does this one support multiple objects? It does.\u003C/p>\u003Cp>So I can select multiple tags for this. This is probably gonna want be what we're looking for there. We can actually probably genericize this as well. We have a string. We have a collection, which is a string.\u003C/p>\u003Cp>This is gonna be our search. This is gonna be a collection. And in this case, our search would be search categories. What's that gonna be? The categories?\u003C/p>\u003Cp>Actually, categories. So it'll just be search. Does that work? Is that gonna give us what we need? For some reason, that doesn't work.\u003C/p>\u003Cp>Undefined. Surf search f n. Do I have, like, another search variable\u003C/p>\u003Cp>Speaker 1: or something? String. Q.\u003C/p>\u003Cp>Speaker 0: Alright. Does that get us what we're looking for? Let's try it again. There we go. Okay.\u003C/p>\u003Cp>Alright. So now we're getting that. Let's copy in our use select menu. Does this have the same async search capabilities? It does.\u003C/p>\u003Cp>Our use select menu, this is going to be multiple, so we'll get our tags. The model equals form dot tags. Our search function here is going to look like this. So we get our search functions. This is going to be tags.\u003C/p>\u003Cp>Search for tags. We've got the option name. It's gonna be a title. That could be multiple. It could be by ID.\u003C/p>\u003Cp>Great. And now we can see, like, full time, part time. We got some tags selected. Great. Alright.\u003C/p>\u003Cp>Let's add some buttons, just a single button to our form group. We'll make it type equals submit. And what are we gonna do here? This will just be submit listing. Okay.\u003C/p>\u003Cp>We've got a job post. Cool. For the sake of this, we've got, like, 7 minutes running on the clock. I am going to allow people to submit a new listing here publicly. I would probably make them log in or create an account first, but, for the sake of just getting this done, let's try it this way.\u003C/p>\u003Cp>And then here, form dot title dot tags. Let's just see what we're getting first. Here's a new post. When I search for my programming category, we'll apply a couple of tags here. Here's the amazing content for our job post, and we just got a new link.\u003C/p>\u003Cp>So httpdirectus.i0. If we look at our form component, let me close this. So open this back up. New is the name of this actual component. Why don't I see it?\u003C/p>\u003Cp>New. There it is. We can see the state of our form. Our tags are an array of objects. Our category is an array of objects.\u003C/p>\u003Cp>Here, I'm just gonna do this where we do category form dot category dot ID. I think that should work here. For our tags, probably going to have to do some type of mapping dot map tag dot tag dot id. Okay. Oh, no.\u003C/p>\u003Cp>I lost my amazing post. We are coming up very quickly on the end. We need an amazing Directus developer and a 100 apps, a 100 hours host. Probably after I fail at this one, we'll do design, full time, SEO, select 5 tags. Here's some content for this post.\u003C/p>\u003Cp>We'll do the direct us URL, and let's see what happens. Alright. We'll do network request. Post. I don't see anything happening.\u003C/p>\u003Cp>Submit listing, submit listing, type equals submit within the form, Submit listing at submit dot prevent listing. Does this need do I need, like, a click handler on this? I shouldn't. Let's try it anyway. Submit.\u003C/p>\u003Cp>Why are we not making a single call here? Submit this. Why are we not submitting? Prevent default. At submit dot prevent.\u003C/p>\u003Cp>And maybe it automatically does that for you. Man. I hate to cut this down to the wire. We are gonna cut it down to the wire. Test.\u003C/p>\u003Cp>Test. Array of errors. We got what? Internal server error. What do we actually send to the server?\u003C/p>\u003Cp>Here's our payload tags 12, application URL, category is 2, company is test tag. What kind of response did we get? Why is this an application error? Job listing, submit listing, direct us to request, create item, category. Man.\u003C/p>\u003Cp>This is frustrating. Form.application.url. Click submit listing. Why isn't this going through? Maybe we unselect these things.\u003C/p>\u003Cp>Can I unselect 1? Submit listing. Errors. Errors. Errors.\u003C/p>\u003Cp>Internal server error. Oh, man. There's something that I'm not picking up on. Create item, job listing, title category, form dot ID. What if we just drop these 2?\u003C/p>\u003Cp>Can we actually get this to submit something? Content. Test. Job listing. We've got the public ability to do the job listing.\u003C/p>\u003Cp>Job listing tags. Is that what it is maybe? Alright. We're just throwing the Hail Mary pass here. You can edit anything.\u003C/p>\u003Cp>You can edit anything. You can edit anything. And now we're still getting errors from the direct instance. Internal server error. Is it like a cores issue?\u003C/p>\u003Cp>You know, if I log out, if I do this in an incognito window, test, test, test. Still getting it right. Items dot job listing response redirected false errors. An unexpected error occurred. What do I have wrong inside this particular setup?\u003C/p>\u003Cp>Title, status. Is it the slug that I made unique? Slug has to be unique. Is that what it is? Test.\u003C/p>\u003Cp>I'm going to kick myself after we run out of time on this one. Item, job, listing, post. What what is going on? Is it a a particular, like, a chorus problem? I thought I had chorus set up.\u003C/p>\u003Cp>Chorus enabled. Chorus origin. This is a oh my gosh, dude. If it is a chorus problem, we're gonna kick myself if it's a course issue. Docker Compose.\u003C/p>\u003Cp>Let's see what we've got here. I see some logs. Error, insert into job listing. Title created values. Invalid input syntax for ID.\u003C/p>\u003Cp>Why is it creating an ID? Should not be creating\u003C/p>\u003Cp>Speaker 1: an ID.\u003C/p>\u003Cp>Speaker 0: I don't know what's going on there. Alright. Let's try this again. Test. Test.\u003C/p>\u003Cp>Test. Error. Error. Error. Database.\u003C/p>\u003Cp>Into job listing. Invalid syntax input for UUID. Like, UUID should be getting automatically populated though. Application URL. What what am I not sending?\u003C/p>\u003Cp>Oh, it's the company. It's the company field. Oh my gosh. The company field is killing us there. Okay.\u003C/p>\u003Cp>Alright.\u003C/p>\u003Cp>Speaker 1: 4 seconds left. Submit. Job Listing.\u003C/p>\u003Cp>Speaker 0: Nothing. Still errored out. Boom. Time exploded on us. Just out of curiosity, it was the company field that was messing things up.\u003C/p>\u003Cp>Now, we're still getting, now I'm getting like course errors or something. Or, no, does it show it posted? Did we actually get a job listing in here? Yeah, we did. Of course.\u003C/p>\u003Cp>After time, the company field getting us, I should have like, actually ran that down. Did not. That's the way these things go, man. It's always hairy trying to develop against the clock. Alright.\u003C/p>\u003Cp>Let's just test this, test with categories. I'm curious if this will actually work now because we have the, now I'm getting cores errors. Alright. This is just me humoring myself at this point. PMPM dev.\u003C/p>\u003Cp>Let's restart the Docker container. Oh, no. We don't need to do dev. Okay. So we restart this Docker container, fire back up this Nuxt application, see if this is actually going to do something.\u003C/p>\u003Cp>Just again, me humoring myself here. Correctus is online. Nuxt application is online. Is this actually going to submit? Local host 3,000, post a job, test with categories, can't even spell.\u003C/p>\u003Cp>Programming, apply some tags. This is a great job. Here's the test. We hit submit. I probably need to add some submission there.\u003C/p>\u003Cp>You don't have permission to access this. Why don't I have permission to access this? I've got public controls. Tags, content is great. Oh, it's because, the Junction collection.\u003C/p>\u003Cp>Oh, boy. Yeah. So, tags is actually a should be something like this where I have tags what's this gonna be? Tags underscore ID equals tags ID. Alright.\u003C/p>\u003Cp>So in that case, now, new is this\u003C/p>\u003Cp>Speaker 1: actually going to be what we want it to be?\u003C/p>\u003Cp>Speaker 0: Full time ID. So when it gets submitted, I'm hoping that should be what we want. Tag underscore ID. Tags underscore\u003C/p>\u003Cp>Speaker 1: ID. No. Save without formatting.\u003C/p>\u003Cp>Speaker 0: What are we doing here? Sometimes you can't win them all. Sometimes you just can't win. Test. Test.\u003C/p>\u003Cp>Does that actually submit? It does submit with the category. So there we go. Roundabout way of getting to this. We still didn't get exactly where we wanted, but, you know, what did we actually get to here?\u003C/p>\u003Cp>I blew this one up. So we managed to show a list of jobs. We let users kinda post a job for free. Were these sorted by category? We didn't even do that.\u003C/p>\u003Cp>Subscribe to job posts, we didn't even get there. Man, alright, that's the way it rolls. Thanks for joining me on this episode of 100 Apps, 100 Hours. I'll catch\u003C/p>\u003Cp>Speaker 1: you on the next one.\u003C/p>","Hi. Welcome back to another episode of 100 Apps, 100 Hours. I'm your host Brian Gillespie and on the show we build some of your favorite apps or some of your suggestions or just random stuff that I've come across on the Internet in 1 hour or less or get publicly shamed trying when we fail. And I fail often. That's just the rub of this format. The rules are there are 60 minutes to plan and build the application, no more, no less. Sweating just thinking about it. But rule number 2 is use whatever you have at your disposal. AI, GitHub Copilot, tailwind CSS, tailwind UI, any pre made components, whatever we can to get the job done in 1 hour. Today, we are going to be building a job board. So the giant elephant in the room in this space is obviously Indeed. They are one of the largest job boards. Yeah. It's been a while since I've been on their site. The experience is not great, honestly. Maybe everybody's working on a a mobile or something, but, I I think the the job board that we're gonna model after today is we work remotely. We are an all remote team here at Directus, so this speaks to my heart. I've been working remotely for, the better part of a decade now. So, we are going to model after this. Let's dive in and get started, shall we? Alright. So we'll put 60 minutes on the clock. Let's just talk through the functionality that we're looking for here. And As I pull up the WeWork Remotely job board, what's our functionality? Right? We want to, obviously show a list of jobs. In this case they are sorted by category. We want to let someone let users post a job for a fee. That's nice. And, you know, this one's got subscribers as well, which seems like something that is relatively simple. There's a lot of moving pieces to that. So let's add a question mark and we'll do subscribe to job posts. Now I think this one does not allow you to apply within the platform. I think it just, like, sends you off to a third party thing. So that's the way we'll play this, you know, if you wanted to build like an application processing system, you could totally do that with Directus. That's a lot of ground to cover in 60 minutes though. So let's take a look at this, let's flesh out our actual data model, right? That's part of the planning process, so I'm just going to drag some boxes over here inside Figma. What are we going to have? We're going to have job posts or we could call it job listings. That's great. Within that, we're gonna have a few different things. Alright. What do we got? We got some tags, it looks like. So we got a category. We got a title for that post. We've got some content, we've got a link, we've got a like, a company. I guess if you're going to have a company, that would probably be here. That'd be a separate table. Right? There's a company. Company has a name, a website. Name. Website. What? Logo. Great. K. What else are we gonna have? If we make it 2 subscribers, we'll worry about that when we come across it, but let's dive in. Right? This seems pretty good. We'll just draw some arrows to make it look fancy. Right? A company has multiple job listings. One listing has one company. Alright. Great. Let's talk about what we're working with. So as far as applications, I've got a direct assistance spun up, it is blank. I've got a bare bones Nuxt app that has some login and register capability. Looks like I'm already logged in though, so all good there. And as far as the Directus communication, I've already got a Nuxt plug in defined just to talk to Directus a little bit easier, make it a little less boring on this specific episode. Alright. So let's start by I'm going to pull this up side by side with remote. Oh, no. That's not what we want. I could spend the whole time just messing with the Arc Browser. Alright. So we're talking about job listings. We're going to go ahead and add that as a new collection. And I think I can zoom in a little bit to make that more visible. And then we'll look at the actual listings that we have. Right? One of the other things that I do often is, I'm not sure how this site is put together, but, you know, on sites that are server side rendered, you can go into the actual code and especially if it's, like, a Next application or, I'm not sure if Nuxt does this or not, but you can actually see the data that's posted. I'm not sure if that's the case here. I see Jquery. This is probably server rendered from an actual server. No big deal. But, just one way you can kind of see what other folks are doing on the website, always inspect the code. I live in the JavaScript developer tools console. Alright, so we're going to have a job listing. We're going to have a pretty slug for all of these. So let's see what we're going to do, we're going to have a draft and publish listings, probably got a date created, did the user created, the date updated, etcetera. And let's go through and model out our listing, right. So we've got a title for the listing, we're going to have that. We've got to have a slug for the listing. So we'll just do a slug for the listing. We'll come back to that one in a moment. What else are we going to add? We're going to have some content. I see there's tags here. I don't know exactly how they're using the tags, but it looks like the the tags get their own page. So in that case, it makes me think we need, like, a relationship there. Let's look at that. We'll do a mini to mini relationship because one post could have many tags and there could be many tags to other posts. Alright, So we got tags as a key. I'm going to enter tags as a related collection here, right? And if I open up the advanced field mode inside Directus and I scroll down to the bottom, you'll see that, it's actually going to create a new table for me. So that tags table that I don't have, it's going to create it for me, which is kind of nice, right? And I can add this to the tags, I can show the job listings on the tags that have that specific tag. And maybe we can sort these as well. Cool. Alright. So we'll hit save. We've got tags. We also should have a tags collection now, which is nice. We have a junction table for those as well. Then we're gonna need some content. Right? We need the actual, content for this. That's gonna be the WYSIWYG editor. Is that great? Cool. The type will be text, that's what it's going to be saved as in the database. I can always adjust my toolbar if I need to. And, what else do we need really? Apply for this position, right? This is going to be a URL. So apply URL, Application URL. That's what we'll roll with. We're probably going to need a category for this as well. So if I go back to all the jobs, we can see we've got sales and marketing. If I zoom out a little bit, we can see all the different categories up here at the top, Programming, Design, etcetera. So we're going to create a new relationship inside Directus. Let's call it category. And we will again create a new collection here. So if, even if this doesn't exist, Directus is going to create this for me, which is, you know, super. It's a really easy, super nice way to do our data modeling. We can add the different job listings to the categories if we want to. In this case, maybe I don't need to show that. But we will, we don't have anything to adjust as far as the interface because this is not created yet, but we'll just go through the process. So we've got a category for this, We've got tags. And if I go back to my data, I'll see categories here now. So we've got categories, we've got tags. Great. What else are we missing on this? Is this a featured post or not? I'm assuming that's part of the checkout process. So, you know, we could add a Boolean tag for that is featured. I always like when I'm doing my keys, especially if they're Boolean prefix with the is. There's just a nice little standard naming convention. And on my interface, maybe the label is featured post. Great. Okay. Alright. So this looks pretty good, I want to say. Alright. We'll make some of these things half width. Maybe we make the title full width. We got the status. Actually, let's do this. We got status, we got title. Let's put slug down below title. And don't sleep on the Directus marketplace. Right? One of the extensions that I've been using a lot here lately is the WP slug interface extension. If you're used to WordPress, you got, like, this nice little slug interface there. We're just gonna install this, give it a quick refresh. Then I go back to my data model, I go to my slug, go to the interface, and I can switch this up to my slug. I get a template for this, so we're going to populate the title. I could potentially prefix this or, you know, auto generate it as well. So I'm just going to do On Create because after a thing is created, it's been published, changing the slug results in a different URL on your front end, could result in some nasty 404 errors. We don't want that. 40 fours get out of here. Alright. So we got a job listing category. We've got did I do job listing instead of job listings? I did. Man. Alright. How can we fix that? We're not going to bother with it. I could dig into the SQL and adjust that if I needed to, but we're not going to worry too much with it right now. Typically, I keep my table names plural. Just a personal preference. Alright. So we've got a category. Let's go ahead and flesh out these categories. We need a title for the category. We probably need a slug for the category as well. So I can go back into my job listings, and if I want, I can duplicate this field to that collection. So we'll just change this to slug. We'll go to categories. Great. And in that case, do we actually want the tags to have their own page as well? The tag will have a title and the tag will probably have a slug as well. So we'll just copy that across. Slug. Change the collection to tags. And away we go. Right? The last thing that we talked about is a company. So we want to be able to select a company for our post and you know potentially a company could post 3 or 4 different times. Maybe they're hiring for a lot of different positions. So let's create a new table. We're going to call it companies. We'll do a generated UUID. Do we need all this information? Probably not. Alright. So we'll give the company name. And whatever I choose to name this key is totally up to me. And then we've got the website. That's just going to be a string. And then we're going to have a logo for that. Right? Logo. Alright. Cool. So now we've got it's just a basic data model set up. Let's go in and I'm just going to, like, just rip some of this content. We work remotely. Sorry for just sealing information off your job board here. Let's look at our different categories. So we'll just mop up a couple of these categories really quickly. We've got what? Programming? Oh, gosh, I can't spell. Programming, save. We've got Design, save. What else do we have? Sales and marketing. Sales and marketing. And what happens if we do this? Sales and? Oh, that's kind of a nice effect. Sales and marketing. Maybe we change this to just be sales dash marketing. Customer support. Customer support. Great. Okay. And this is all remote, so, you know, what are we gonna call this thing? Maybe we'll call it it, Dan's Remote Job Board after some of our own teammates here at Directus. Alright. So we've got some categories. Let's take a look at the tags within here. We've got full time. We've got, part time will be a tag. Right? Part time. What? SEO? Link building, WordPress, technical writing. What do we have on the development side? So we got programming. We got full stack, front end. What kind of tags do we have here? Europe only, etcetera. Hang on just a moment. I got an important call coming in. Be right Alright. So now we're back. We've got 46 minutes on the clock. Let's dive in further. We've got some tags. We've got like Europe only, etcetera. Great. Okay. So, those are a few tags. We've got some categories. We've got, let's see if there's any companies that we recognize on here for full stack development. I don't really recognize any of these. Right? Let's just create a new company here. We'll call this Directus HTTP Directus dot io. And we'll add the logo for this. Right? See what I got handy. Directus logo. We'll get this a couple of different ways. Logotype. That looks good. That's what we will roll with. We got the logotype. Maybe we add another company, but for now let's just add our first listing. Right. I'm just going to steal this one, full stack. Alright. We are going to make sure this is published. Is this a featured post? Looks like it is. Alright. We'll go in. And I could just copy this wholesale. And I'm thinking it should retain some of the content, some of the settings. Yeah. Okay. So it should retain the formatting for it. And then we're going to have a link for the application. And the last thing that we need to do is pick a category. And we probably need to add a company for this as well. And I I joked, it's not the last thing we need to do. We're going to add a few more. This is going to be a Europe only job. Be a $100,000 or more USD. That's that's kind of a nice tag. Let's do React as a tag. Oh, sorry. Creating an item in a tag. React. Great. Alright. Is this a featured post? Yes, it is. Let's save that post and then we'll go in and add a relationship to that company. So that'll be a many to one relationship inside Directus. We're gonna call this company because there's a single company, and then we have companies. And, you know, we could go in and add the company name here so that it displays nicely. We probably want to do the same thing for our categories, like give them a title, make sure those show on the interface in the display template. Same thing for tags. For our list view here, we're going to do the tags ID dot title and we'll do the same thing here. Great. Okay. So now if I open up this job, we can see our different tags for this. We've got our category. We've got the company that this is created for. Sweet. Alright. So now we've got a job listing. We've got some categories, we've got some tags, we've got some companies. Does a company going to have a slug? You know, potentially. Do they have a listing here? They do. So probably need a slug for the company as well. Slug. The template is going to be the name of the company. Great. Alright. So we've got our data model for this, right? The next thing that I'm going to do is just go through our access control and create this. So there are 2 roles that are out of the box inside Directus. I have public, I have administrator. I'm going to go through and, just basically set read access to all these. And then I can go back in and adjust this if I need to as well. So we're going to adjust the item permissions just a little bit to where we can only see published posts. So I'll go through listing tags, categories. I don't know the categories. Do we do publish for categories? We did not. Tags, etcetera. Yeah. So the rest of those are fine as is. Right? People can see all the companies, all the tags, all the categories. But if it's not a published listing, they can't see that information. Alright. So great. Let's go in and now, is it time to actually start fleshing something out inside our Nuxt application? Let's let's take a look at that. Right? So we've got here on the how do I separate these 2? Alright. Cool. So, Nuxt application on our pages directory, we've got an index page. This is probably going to be just our listing of posts. Right? So we can start fleshing this out a little bit. Let's remove this centering. That should move it way up here to the top. We'll just add a header for this. And what are we going to have in the header? We're going to flex. We are going to do Dan's remote job board. That's the name of this thing. We'll make sure it's like a mono font for Dan. I know how Dan likes this stuff. Okay. Let's give this header a little padding. PX6 PY. Alright. And we can even make this like sticky at the top if we want to. Sticky, top 0. Give it a background white. BG opacity. We do that backdrop blur effect. One of the nice things about using tailwind, backdrop blur, BG opacity. I could actually just adjust it this way. Let's do dash or slash 50. And cool. Alright. So now we've got a header. We might have some links inside our header that we'll cover in just a moment. Let's do a div to ul. Links. Do we actually need these on a list? Probably not. Let's do what? Nuxt link v 4, links and links, links on links on links. Let's actually call this nav links. And we'll just flesh a few of these out real quick. Nav links, that'll be an array equals array. To, about, contact, no. That's we want none of that. Home, we got what? Categories. We'll come back to it. Let's just nuke that for now. Or we could just comment it out. Alright. As far as what we want inside the actual body of this here, we'll do this in the main section, we are going to fetch our job listings, right. So Nuxt has a couple of nice composables for fetching data. We're going to use that here. But the first thing I'm going to do, let's import the read items operation from my Directus SDK. Nope. Directus SDK. And then I'm going to use the async data call, the async data composable from Nuxt. Use async data, And this handles like caching and deduplication, and actually if this is server rendered it will automatically dedupe those calls and pass that data from the server to the client so that I don't have to fetch the data twice and hit that API. So we'll give this a, let's say this is listings index. We'll give it a key and then we'll actually fire this off. And we're going to actually await this data. Alright. Then within here, we're actually going to use our Directus client that we've set up. We're gonna access that through our Nuxt plug in and we are going to do this. So we'll say return Directus dot request. We'll do read items. We're gonna read from the what do we call it, actually? Call it the Job Listing Collection. So that's going to trip me up this whole time. And then we have a query that we could set up. We could run this where it says filter status equals published, but I'm handling that here within my access control inside Directus, so it doesn't really matter. So as long as this is published, like if I go in and let's just say I change this. Change this. We say we maybe set this to draft, and I hit save as copy. And I probably need a new slug for this. And I would definitely make slug unique here, so let's just do that quickly before I forget. We'll go into the data model, we'll go to listings, we'll make sure that slug has to be a unique value. Alright. So this will be listings and then I can I typically do something like this whenever I'm building just to quickly and easily make sure I'm getting all the data that I want back from my API? Alright. So we go to local host. We can see we've got this job board and I need to actually get this out of the header and into the main body here. Alright. So there's our application, our job listing, basically. Alright. That's great. We can see our tags, we can see the company. We can't really see any of the information about them, though. Right? And if we take a look at our postings here, let's see what we've got. We've got the company information, we've got the company name, etcetera. So how do we go and fetch that data with inside Directus? We'll just go in here. We'll do something like this where we have fields. I don't recommend doing it this way in production, just using these wildcards. But for something like this where I'm quickly prototyping, super handy for that. I could just get all of the root level fields, I can get all of the company level fields, I could get the category, category dot star. And then for our tags, I'm going to get tags dot tags underscore ID. I'm going to get that field. So if I do that now, you can see I get the extra information that we're looking for. So I've expanded my relationships for tags, for the company, for our category, basically everything I need here. Looks great. Looks great. Okay. And I'm getting both of those things here because I am using session authentication. I've got a cookie here that's storing my session token. So if I wanted to, I could go into incognito window and that should solve it where I'm only getting one of those items back. Alright. So I'm gonna pull this up. We work remotely over here on the side just so I can see that. And we'll start fleshing this thing out. So within our listings, we've got, let's do a div. Probably add, like, a h two here. This is our job listings. Get that font bold. Text XL. Maybe 2 XL. Alright. And maybe we give the whole main section a little bit of padding. I I think there's a U container Nuxt UI component that we'll just use for this. I'm using uidot nuxt.com, just a Nuxt UI library, just to have something to build with quickly. Alright. So we've got our job listings. Let's go through we're gonna do a card component for each one of those listings. So v 4 job in listings. The key, we can use the job dot ID. And here, inside the card itself, let's see what we got. Job title. Is this actually gonna be what we want? What's this gonna look like? Remote. It's not really anything that we we actually want here. This is gonna be company name, category dot name, Category dot title. And and this could be remedied by TypeScript if I had all these things typed probably. But as far as the tags, that's going to be the title of the tags as well. Cool. Alright. So how can we get this to look a little more like what we have? We'll go through and flesh this out a bit. Wrap this. We'll put it in a, it doesn't even need to be a grid. We could just do like a single grid. Give it some gap. 6. Alright. We got some space there. We'll give it a little breathing room. Cool. Alright. So we've got some job listings. We're going to want to give these a link. Where did where did that go? Oh, just disappeared. Need to learn my keyboard shortcuts, don't I? What are we what are we doing, Brian? That did it again. Alright. Maybe I'll just use the cut paste here. Wrap this in a Nuxt link component. We're gonna do this, the 2 for this will be something like this, where we have listing, plus job ID. Yeah. Potentially let's actually do it this way, just use a template literal. Job dot slug. Wrap that, don't forget the ending tick. Cool. And now we have a link that does not work because we don't have a route for that. But let's see if we could show the company information as well. Maybe we put that here. It would be within a div. We got the company name. Show Nuxt image source dot company logo. See if we can get that displaying. Okay. Now we'll shrink that a bit. Object Oh, hang on a moment. Just go here. Class, object contain, make it 24 high, 24 wide. Cool. Within this, we could do, like, a flex, add a little bit of gap. And, you know, if we wanted to, we could get fancy with it. But let's, how do we set this off a little bit? Oh, actually, the title for the company is over here on this side in it. Alright. So go in, wrap these in a div. That should give us like some type of formatting here. And if I really wanted to make it look like the one on WeWork remotely, and I don't, we'll just add a border for this, round it a little bit, get a little bit of padding, p 1. And maybe we make the border pretty. Make it violet. There we go. That looks poor, actually. I don't like it. Alright, we'll make it more subtle. There we go. So we got Directus, we got Change This as the name. We've got our job listings. How are we doing on time? Time wise, we're at about half an hour, right? Alright, so we've got some job listings. We can see a list of those. How would we do something like a filter for these? Or we'd probably actually want to list these by categories. Right? So we'd probably actually switch up our data fetching here into, we'd probably look for categories first. But instead of diving into that specifically right now, let's actually get a detail page up. So we've got a route for this index page. Let's go in and add a route for our listings. Great. And then we'll go in and add a new page. Let's call this the slug. View. So we do have a listing there. And maybe I just copy pretty much all of this. We'll actually, what do we wanna keep here? Let's just keep the card component. That's got our information. Maybe we keep the u container just so we get some base styling. And if I wanted to, I can move this header component out of this and into our actual app itself or in probably like the default layout would be a good one as well. Just post that there. So now that will show on every single page. Alright. So as far as our job listing, we're going to fetch that data here. I'm still going to use the read items call, because we're fetching by the slug and not the actual ID. So what we're going to do inside Knox would be something like this, where we use the route composable, or use route composable to get the route. And then we're going to set up a filter for our slug, and we want that to be equal to route. Params.slug. So this should give us our listing. And then what we're going to do here is at the end of this, using the use async data and the I think it's use fetch composable inside Nuxt have an option set where I can transform this data when I return it. So this is going to give me an array and I only want the first item of the array. So even though this only matches one thing because the slug is unique, it still returns an array because of the read items SDK, or the read items method of the SDK. So we're just gonna transform that. And then here inside our card, we don't really need that anymore. Right? So this gives us what we want. Cannot read properties of undefined of slug. Why do we not have this? Right? Here's our listings. There's our slug. Route.params.slug. Directus.requestreaditemsjoblisting. Cannot read properties of undefined of slug. Let's just comment this out, see if the page actually loads. Yes. It does load. Let's figure out why we're not seeing that. So, the Vue dev tools is is really a great tool if you're developing with Vue. We will look for the slug component. Let's see what we got. We got listings. Oh, that's why. That would make sense. Right? Sometimes it's the typos, especially when you're building quickly. They get you into trouble. So here, let's just remove the card. I don't even need this Nuxt link component anymore. Okay. And within our layout, let's make it let's get some navigation so we can actually move backwards here. Make this a Nuxt link. Nuxt link. Change it to 2, do slash. Cool. So now I could go should be able to go back and forth. There we go. Go here. Go there. Amidst options next to header slot. What am I doing wrong there? Alright. Anyway, so this is gonna be our job. And now we have got our job listing. Cannot read properties of type. Alright. Let's go back into Directus. And is this because of our our access control? We've got it. Let's set it up so we can read the files as well. Does that give us what we want? Okay. So at least now I've got some type of card information here. How do we want to display the actual content for this? If we go into one of these, we've got, the job title, the logo here is on the right as well as the company. So we can go in and flesh this out. You know, we could spend a ton of time on design here. I'd rather focus more on the important bits. So let's just do a main component. This is where we'll stuff the content. We could do v dot vhtmlordiv.vhmml, v dash html. That'll be our job dot content. Okay. That should show. Cool. This is not really nicely formatted. Right? So, using Tailwind, they have this really nice pros class we could use to get that formatting. Looks pretty great. Maybe we add some padding to this container. I don't even need that. Strip this away or not some padding. Give it some margin. This looks a little odd here at the top. Let's remove that. We've got Directus is the company. And this could be another NuxLink. This could be, what, to the company? 2/company/job.company.slug. That's what that would look like. And inside this div, maybe we just add some spacing. Okay. Alright. So now we could potentially navigate to the company, but there is no page for that. But here we've got our actual job posting. How do we we want to use like a button for you button. Is it the apply URL? I think I've got application URL here. Apply for this position. Great. What's that gonna do? Job application URL, is that what I set this up as? Let's take a look at our data model. Application URL, job application URL, that should be correct. And we want this to be let's make this a block class. Block. And maybe we drop that below the actual name. We don't have a description for this. It's great. Maybe we do this. Just set off this metadata just a little bit. Okay. We hit apply for this position. And this should open that in a oh, it's because I've got HRF. Let's try this. Apply for this position. That should give me this application page. Great. What is this going to be? In line block? There we go. Okay. So now we've got our page, we've got our company information, we've got our job description. We can drop another button at the bottom of this content. Looking nice. Alright. We are cruising on, like, 20 minutes worth of time. Right? So how can we actually create a post? Right? We've got our post. Let's go back and we'll do a new route. We'll just call it listings dot new. I'm just going to copy the same setup that we've got. Just moving really really quickly. Just drop everything out of here. We're gonna keep Directus. In this case we're gonna use create items. Create item. Great. And on the front end, if I wanted to be able to create a listing, we're going to need to add some navigation for this. So we'll just, let's actually stick this inside our header. Right. Where are you? That's in our default layout. So over here on the far right, we can actually flex these items, justify between. Oh, sorry. I already had the flex. Great. And we'll do listings dot new, post a job. Great. That takes us to the job new page. And on the new page, why do we not okay. Okay. So now we are going to post a job. We've got let's take our same heading here. Post this in here. We've got a create a new job post. Okay. Alright, so now we can start fleshing out our actual form that we want, right? So let's just do form. This could be reactive since we've got some properties here. What all do we have inside that listing? Right. We've got a title. You know, maybe we don't want them to actually choose the slug. We've got a category. That should just be an ID. We got some tags. This is gonna be an array. We got some content. That's gonna be a string. We got an application URL, and we've got a company, which is, it's actually, should be a string, just an ID, but we'll have to actually fetch that information. And then at the end of this, we are going to have a function to submit the form. Alright. Submit listing. And what's that function gonna look like? Oh, let's do a try catch. This is gonna be a constant response equals await, direct us dot request. Create item. Yes. This looks good. Form dot title. Okay. Alright. GitHub Copilot. I got you. And then what are we gonna do? Console log response. And we could send them to like a thank you page or something. Maybe we are tracking like a success state here. And we set success value to true. If there's an error, we're going to log that error. Great. Cool. Alright. So we got submit listing. Now we need to actually build a form. Right? Inside this Nuxt UI library, I think there is a, let's just dive into it, form component. You can even add validation with this using, like, Zod or something like that. But, we've got our form wrapped in a form. There's a form tag. We've got at submit. Let's do dot prevent. Submit listing. That's going to be our form handler. And then inside the form, each one of those gets wrapped in a form group, where we got a label equals title. Placeholder equals, amazing Directus developer. That'll be our placeholder for this. So we that's probably actually on the actual input itself. Alright. And then we have a u input form dot title. We have a placeholder. Let's see what we're getting now. Cool. Amazing Directus developer. Maybe our form isn't so wide. 3 x l. Did that bring it in? It did not. Math would okay. Still a little long, but no worries. Okay. So we've got our title. We want to go through and add the rest of our form. We got a category. Let's see what GitHub Copilot can do here. Yeah. It's not giving us a lot of help here. But we can drop some of these inputs. Maybe we add some spacing between our form components here. So we got tags. We got content. We've got an application URL. Let's make sure we get that. Application URL. And, alright. So for this, like, the company information, the category, etcetera, This could be a list of companies. Right? So what do we have inside Nuxt UI for something like this? We've got a drop down. Is that what we're looking for? Does this have a search? No. That's gonna be input, input menu. I'm assuming this could be an input menu based on the preset. This is searchable. Search attributes, control the query, Async search. Okay. Here we go. So we got a async search function. We can search for a company inside Directus with this. This will be an async function. Let's call it search categories or search companies, maybe. Let's do the company first just to, well, actually, let's do categories. We have them fill out the the company information. Constant, what our data is gonna be await, direct us dot request. That's gonna be read items. We're probably gonna need, what, like, a search query as well. That'll be passed here. This will be our string. Loading value, read items dot categories or categories is gonna be here. Inside that request, we're gonna need a filter. Actually, let's use a search for that. And we'll just do search query. Now the search param here is not optimized like the filter params are, but let's see what we've got here. You form group, you input menu. Oh, wrong one. We want this to be our let's drop this in our category. You input menu, search equals search categories. This is gonna be form dot category. Search for a category. And we can set a loading state for this as well. Alright. Let's see what we've got here. Search for a category. In line properties, not length. If I do this, is it actually searching for a category? Programming? I don't see it. Search search categories, loading, form dot category, placeholder, option name, attribute is a title. Title. Alright. Let's see what else we got here. I'm missing something here. Oh, forgot to return something from that actual function. Gotta return the data, Brian. Makes sense. Alright. See if we got anything else now. Is this actually working? I don't see it calling any of the data, though. Direct is at request, read items, categories. Did I, oh, that's why. Got to import read items as well. Alright, so we refresh. Programming. Okay. Now we see sales and marketing. And inside my actual listing, right, where's the form? So we got our slug. This is actually new. Alright. So we got our form. We got the category. We need the ID of that category. Selecting the actual object. Option Attribute Title. Category dot title? Is that going to give us what we want? Oh, no. Option attribute by ID. Why is it not storing when it selects the value what do we actually want here? V model async search. Use the debounce. You know, we could add a debounce to it. Let's actually deal with that in a moment. We need to actually submit this form first. Alright. For the input menu, what else do we have? Okay. I see like a select menu here that is probably very similar to this. Does this one support multiple objects? It does. So I can select multiple tags for this. This is probably gonna want be what we're looking for there. We can actually probably genericize this as well. We have a string. We have a collection, which is a string. This is gonna be our search. This is gonna be a collection. And in this case, our search would be search categories. What's that gonna be? The categories? Actually, categories. So it'll just be search. Does that work? Is that gonna give us what we need? For some reason, that doesn't work. Undefined. Surf search f n. Do I have, like, another search variable or something? String. Q. Alright. Does that get us what we're looking for? Let's try it again. There we go. Okay. Alright. So now we're getting that. Let's copy in our use select menu. Does this have the same async search capabilities? It does. Our use select menu, this is going to be multiple, so we'll get our tags. The model equals form dot tags. Our search function here is going to look like this. So we get our search functions. This is going to be tags. Search for tags. We've got the option name. It's gonna be a title. That could be multiple. It could be by ID. Great. And now we can see, like, full time, part time. We got some tags selected. Great. Alright. Let's add some buttons, just a single button to our form group. We'll make it type equals submit. And what are we gonna do here? This will just be submit listing. Okay. We've got a job post. Cool. For the sake of this, we've got, like, 7 minutes running on the clock. I am going to allow people to submit a new listing here publicly. I would probably make them log in or create an account first, but, for the sake of just getting this done, let's try it this way. And then here, form dot title dot tags. Let's just see what we're getting first. Here's a new post. When I search for my programming category, we'll apply a couple of tags here. Here's the amazing content for our job post, and we just got a new link. So httpdirectus.i0. If we look at our form component, let me close this. So open this back up. New is the name of this actual component. Why don't I see it? New. There it is. We can see the state of our form. Our tags are an array of objects. Our category is an array of objects. Here, I'm just gonna do this where we do category form dot category dot ID. I think that should work here. For our tags, probably going to have to do some type of mapping dot map tag dot tag dot id. Okay. Oh, no. I lost my amazing post. We are coming up very quickly on the end. We need an amazing Directus developer and a 100 apps, a 100 hours host. Probably after I fail at this one, we'll do design, full time, SEO, select 5 tags. Here's some content for this post. We'll do the direct us URL, and let's see what happens. Alright. We'll do network request. Post. I don't see anything happening. Submit listing, submit listing, type equals submit within the form, Submit listing at submit dot prevent listing. Does this need do I need, like, a click handler on this? I shouldn't. Let's try it anyway. Submit. Why are we not making a single call here? Submit this. Why are we not submitting? Prevent default. At submit dot prevent. And maybe it automatically does that for you. Man. I hate to cut this down to the wire. We are gonna cut it down to the wire. Test. Test. Array of errors. We got what? Internal server error. What do we actually send to the server? Here's our payload tags 12, application URL, category is 2, company is test tag. What kind of response did we get? Why is this an application error? Job listing, submit listing, direct us to request, create item, category. Man. This is frustrating. Form.application.url. Click submit listing. Why isn't this going through? Maybe we unselect these things. Can I unselect 1? Submit listing. Errors. Errors. Errors. Internal server error. Oh, man. There's something that I'm not picking up on. Create item, job listing, title category, form dot ID. What if we just drop these 2? Can we actually get this to submit something? Content. Test. Job listing. We've got the public ability to do the job listing. Job listing tags. Is that what it is maybe? Alright. We're just throwing the Hail Mary pass here. You can edit anything. You can edit anything. You can edit anything. And now we're still getting errors from the direct instance. Internal server error. Is it like a cores issue? You know, if I log out, if I do this in an incognito window, test, test, test. Still getting it right. Items dot job listing response redirected false errors. An unexpected error occurred. What do I have wrong inside this particular setup? Title, status. Is it the slug that I made unique? Slug has to be unique. Is that what it is? Test. I'm going to kick myself after we run out of time on this one. Item, job, listing, post. What what is going on? Is it a a particular, like, a chorus problem? I thought I had chorus set up. Chorus enabled. Chorus origin. This is a oh my gosh, dude. If it is a chorus problem, we're gonna kick myself if it's a course issue. Docker Compose. Let's see what we've got here. I see some logs. Error, insert into job listing. Title created values. Invalid input syntax for ID. Why is it creating an ID? Should not be creating an ID. I don't know what's going on there. Alright. Let's try this again. Test. Test. Test. Error. Error. Error. Database. Into job listing. Invalid syntax input for UUID. Like, UUID should be getting automatically populated though. Application URL. What what am I not sending? Oh, it's the company. It's the company field. Oh my gosh. The company field is killing us there. Okay. Alright. 4 seconds left. Submit. Job Listing. Nothing. Still errored out. Boom. Time exploded on us. Just out of curiosity, it was the company field that was messing things up. Now, we're still getting, now I'm getting like course errors or something. Or, no, does it show it posted? Did we actually get a job listing in here? Yeah, we did. Of course. After time, the company field getting us, I should have like, actually ran that down. Did not. That's the way these things go, man. It's always hairy trying to develop against the clock. Alright. Let's just test this, test with categories. I'm curious if this will actually work now because we have the, now I'm getting cores errors. Alright. This is just me humoring myself at this point. PMPM dev. Let's restart the Docker container. Oh, no. We don't need to do dev. Okay. So we restart this Docker container, fire back up this Nuxt application, see if this is actually going to do something. Just again, me humoring myself here. Correctus is online. Nuxt application is online. Is this actually going to submit? Local host 3,000, post a job, test with categories, can't even spell. Programming, apply some tags. This is a great job. Here's the test. We hit submit. I probably need to add some submission there. You don't have permission to access this. Why don't I have permission to access this? I've got public controls. Tags, content is great. Oh, it's because, the Junction collection. Oh, boy. Yeah. So, tags is actually a should be something like this where I have tags what's this gonna be? Tags underscore ID equals tags ID. Alright. So in that case, now, new is this actually going to be what we want it to be? Full time ID. So when it gets submitted, I'm hoping that should be what we want. Tag underscore ID. Tags underscore ID. No. Save without formatting. What are we doing here? Sometimes you can't win them all. Sometimes you just can't win. Test. Test. Does that actually submit? It does submit with the category. So there we go. Roundabout way of getting to this. We still didn't get exactly where we wanted, but, you know, what did we actually get to here? I blew this one up. So we managed to show a list of jobs. We let users kinda post a job for free. Were these sorted by category? We didn't even do that. Subscribe to job posts, we didn't even get there. Man, alright, that's the way it rolls. Thanks for joining me on this episode of 100 Apps, 100 Hours. I'll catch you on the next one.","published",[135],{"people_id":136},{"id":137,"first_name":138,"last_name":139,"avatar":140,"bio":141,"links":142},"791e1503-1d88-463d-9347-0b9192933576","Bryant","Gillespie","9013afc8-e8d7-4182-9b18-44db08117bb9","Developer Advocate at Directus",[143,146],{"url":144,"service":145},"https://directus.io/team/bryant-gillespie","website",{"service":147,"url":148},"github","https://github.com/bryantgillespie",[],{"id":151,"number":152,"year":153,"episodes":154,"show":164},"14fda5f2-95de-4dbe-a4e2-3fd956c21c19",2,"2024",[155,156,157,158,122,159,160,161,162,163],"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","d072a935-906e-4208-a5dc-e9b117d0ab29","b9f1d4cf-f53c-49db-9e87-adf7e3b9ff99","aad8d674-2b58-4604-8e43-b98f7c6e05cb","6fb9aa9a-2b59-44b6-b78f-d1831fa657c6","620cf225-a23a-415a-ad95-9ba8e2dec984","b8b36125-7a4a-40e4-85f6-f4fe9138085e","385bdd7d-038d-4f9c-8037-357e5272420a","383c24d5-b6b5-4d66-aba6-6997af5f77b4",{"title":165,"tile":166},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"title":168,"meta_description":125},"Building a Job Board for remote workers with Nuxt and Directus",{"id":170,"slug":171,"season":172,"vimeo_id":173,"description":174,"tile":175,"length":176,"resources":8,"people":8,"episode_number":177,"published":178,"title":179,"video_transcript_html":180,"video_transcript_text":181,"content":8,"seo":182,"status":133,"episode_people":183,"recommendations":185},"ec88bef1-fffd-43eb-9d93-3123dc381b97","ai-letters-to-santa","d6b229fe-38fc-495b-ba0c-c574ebfea38f","1059428648","Bryant builds a holiday-themed app that generates personalized letters from \"Open Source Santa\" based on GitHub profiles. Watch as he creates a system that analyzes developers' repositories, determines whether they're on the open source naughty or nice list, and generates snarky, sarcastic letters from Santa — complete with festive styling and holiday cheer.","6209314e-e6ee-4a2d-9e97-11eedd08595a",59,1,"2025-03-10","Mission: AI Letters to Santa","\u003Cp>Speaker 0: Alright. Alright. Alright. We are back with the Christmas edition of 100 apps, one hundred hours. Today, we are going to be building AI letters from Santa.\u003C/p>\u003Cp>I've got my lumberjack style on today. My wife called this lumberjack Jesus earlier, but I digress. We're back for more. The rules of 100 apps, one hundred hours. If this is your first show, we have sixty minutes to plan and build an application, a website, a portal, whatever.\u003C/p>\u003Cp>Whatever we're building, sixty minutes, no more, no less. And rule number two, the anti rule, use whatever you have at your disposal. And since this is an AI Christmas special, I'm gonna pull out all the stops. So let's dive right in. We're gonna hit the clock here.\u003C/p>\u003Cp>Fire it up. Sixty minutes on the timer. Go. Alright. So AI letters from Santa.\u003C/p>\u003Cp>What do we actually want out of this? So I have to admit, I cheated a little bit because I thought about this with my team, and I knew we wanted to do this. I've seen things in the past where you write a letter to Santa, you get something back in the mail, etcetera, etcetera. With AI, we could take this up a notch. So combining two ideas.\u003C/p>\u003Cp>A while back, I saw a GitHub roast page where you enter in your GitHub profile and it, basically will scrape that and give you a roast of how well you're actually not doing in GitHub. So we're gonna combine that with a letter from Santa. And basically, what we wanna do is, enter a GitHub profile. We're gonna scrape that profile. We're going to send that to AI.\u003C/p>\u003Cp>So we want LLM analysis of the profile. I'm not sure what we're gonna call that. And then I'm going to bucket people on the open source naughty or nice list. So score naughty or nice list. And then we're gonna generate a letter from open source Santa.\u003C/p>\u003Cp>Generate a letter from open source Santa to that GitHub profile to that profile. Alright. So as far as that functionality, this looks pretty good. Right? What are the tools that we're gonna use of the trade today?\u003C/p>\u003Cp>I've got a Directus Docker container up and running locally. Directus is obviously the back end we're using to store all of these things. And if everything works as intended p m p m g. I guess, sometimes things don't work as you intend. I've got a Nuxt application that we are going to try and use here.\u003C/p>\u003Cp>I'm not sure what's going on, but let's hop into the Directus instance. So I'm just gonna pull up Chrome. We'll log in to 8055, and I should be able to pull up my back end. So great. Got Directus running.\u003C/p>\u003Cp>You could see this is a pretty blank instance of Directus. This is just the boilerplate I use now. There are a couple extensions installed that I was testing, just messing around with. But, let's make sure. What are we doing here?\u003C/p>\u003Cp>For MPMI. Sometimes these things never go as planned. Okay. So maybe now we can get this Nuxt application up and running that will be served at local host 3,000, and we'll just basically use it to scaffold out our communications. As far as what I'm using, I've upgraded this boilerplate that I've used for 100 apps to, the Nuxt UI v three alpha, just to play around with Tailwind four and, you know, some of these nice new components that are coming from, like, Radix view.\u003C/p>\u003Cp>So alright. Let's actually model this thing out. Right? What do we need as far as our data models? I think we just need, like, a maybe like a profiles.\u003C/p>\u003Cp>So under profiles, we would have, what, our username, letter from Santa, letter from Santa, list, you know, are you naughty or nice? Great. And what else do we need? Let's let's jam on that. So we'll, just set up the back end for this.\u003C/p>\u003Cp>I'm just gonna create a new collection. We're going to call this profiles is the name of it. And why can't I zoom way in? There we go. That's maybe too far, but all good.\u003C/p>\u003Cp>Let's do created at, updated at. K. Status sort not needed. Who this was created by, I'm not super concerned with. So now we have a profile.\u003C/p>\u003Cp>We're gonna do the username. Great. That's where we'll store the GitHub profile. What else do we need? What else did we have here?\u003C/p>\u003Cp>We've got the letter from Santa. What is that gonna be in letter from let's just call it letter. Great. We'll use the WYSIWYG editor inside Directus so we could just store, I'm assuming, HTML content for that. And then we've got the list.\u003C/p>\u003Cp>So that's basically gonna be a string. We can, you know, make this look nice inside the directus admin. We'll just give it a naughty. Feel naughty just typing that out, and then we have the nice list. Great.\u003C/p>\u003Cp>There we go. And I'm just going to go on record that we're probably as soon as we start typing naughty into the AI stuff, we'll probably get some some things back. Like a I probably set off the content alarms or something like that. So there we go. We've got a username.\u003C/p>\u003Cp>We've got a letter. We've got a list. You know, I could potentially put that in here. What I'm gonna do now, I'm just gonna let's just we're just using Directus to store this at the moment. Right?\u003C/p>\u003Cp>There's a lot of different ways I could go with how to actually generate the application here. But Directus allows me to create custom extensions. What I'm gonna do here is just start, working on this from the Nuxt side of it. So we're gonna input the, actual form here. Let's add a profile.\u003C/p>\u003Cp>What are we gonna call this? Let's just call this letters dot view. We'll get a view component set up. Lang equals TS. Great.\u003C/p>\u003Cp>I need to work on my little macros here. Okay. Alright. The other thing that you'll notice here is that I am using cursor. So cursor I recently started testing this thing out.\u003C/p>\u003Cp>Really enjoying the actual auto completions for this thing. So, I don't have it usually generate like a a giant list of code, but the automations, or the auto completions are are pretty nice for this thing. So let's start with, what, step one. It'd be enter GitHub profile info. GitHub username.\u003C/p>\u003Cp>Alright. So the only thing here, sometimes it gets a little wonky with the okay. So we use the you form from Nuxt UI. Good question, Brian. The new one, the alpha, they changed some of the conventions.\u003C/p>\u003Cp>So I've got a form with a schema. I've got a form field instead of a form group, and then I've got an input. Okay. So we've got the form, new form field, and input GitHub username. Let's just see what that gets us on the front end.\u003C/p>\u003Cp>We're gonna go to this page, which is letters. Okay. Alright. So let's go ahead and just center this up. I think there's actually a container component we can use.\u003C/p>\u003Cp>Great. Cool. Okay. So now we have a GitHub username, and let's add a submit button. New button, click handle submit, and boom.\u003C/p>\u003Cp>We have a GitHub username, blah blah blah. Hit submit. Supposedly does something. What it's gonna do right now? Absolutely nothing.\u003C/p>\u003Cp>Alright. So the next thing that we wanna do, let's kick this thing off. We want to have the form state, we use reactive for that. Great. GitHub username.\u003C/p>\u003Cp>Okay. And then we're gonna write a function to handle submit. Thank you. Yeah. Great.\u003C/p>\u003Cp>We'll just, console log that. Right? Boom. There we go. We can see the GitHub username, yada yada yada.\u003C/p>\u003Cp>Alright. This is actually gonna be an async function. Great. Okay. So now what do we wanna do with this?\u003C/p>\u003Cp>Right? We have to think about our application structure. And what I'm gonna do here is just basically add a Nuxt server route. So if we break this down, in this server route, what we're gonna do, call the GitHub API, call GitHub API. We're gonna wanna grab a couple pieces of information like the user profile, or any other repos, and maybe, like, their their public read me.\u003C/p>\u003Cp>I guess we could loop through the actual repos and, you know, pick up more information there, but, let's see what we can get done with that piece. Alright. So let's just go here. We're gonna set up a new route. Let's call it, roast route.\u003C/p>\u003Cp>We'll do post, and just gonna copy the event handler here. K. So now whenever we hit this route with a post, it should return hello world. We can just check and see if that's actually gonna work. So, we will do the I'm trying to think if that's gonna return.\u003C/p>\u003Cp>Nope. So we got the response. We're gonna do await. We can use the regular fetch or the dollar sign fetch, which is the Nuxt specific version. And just test this out, see what we get back in the console.\u003C/p>\u003Cp>Where are you? Okay. Yeah. So we can see the request going out. We can see hello world coming back.\u003C/p>\u003Cp>Great. Cool. Alright. So now what we're gonna do, right, let's just scaffold this out. We are going to pick up the body.\u003C/p>\u003Cp>There's a wait read body. Great. So that is going to have the GitHub username in there. And, how did we spell that? Yep.\u003C/p>\u003Cp>Great. Alright. So we're gonna say GitHub username, and then we've got, like, this git roast function. I'm not really sure where some of these auto completions are coming from. But, what we're gonna do next, let's call yep.\u003C/p>\u003Cp>There we go. That's a good one. API users, GitHub username. Is that the correct one? Let's just test that.\u003C/p>\u003Cp>All the developers on my team are screaming and crying at the moment, watching all these AI auto completions. So that seems fair. Great. And let's actually use the Nuxt equivalent. Just this is using OFETCH, which does some automated data transformation and should automatically throw errors for you as well, which is nice.\u003C/p>\u003Cp>So this is gonna be the let's call this a profile. Alright. And then if we take a look at the profile, we probably wanna get the actual repos for that user as well. Alright. So we'll get the repos.\u003C/p>\u003Cp>Great. And let's just take a look at the data we're getting from the actual repos. Okay. So what do we actually concern ourself with here? Do we actually want all of this information?\u003C/p>\u003Cp>What do we actually care about from these? So stargazers, watchers counts, maybe those properties. You know what? Let's just jam it all in there and see what comes out of it. Right?\u003C/p>\u003Cp>And then let's get the profile readme. GitHub user content, GitHub username, repos dot name, Repos dot name. No. That's not gonna cut it. I think it's gonna be, what, GitHub username.\u003C/p>\u003Cp>GitHub username. Somebody who's already done this before. Give me the structure. And then main. Let's just see if we can find that.\u003C/p>\u003Cp>Read me will just populate my name. I don't even know if I have a actually have a read me. Yeah. There we go. Okay.\u003C/p>\u003Cp>So that is the structure. Great. That is what we needed to confirm. And now let's just actually return this and see what we'll get back. Alright.\u003C/p>\u003Cp>Brian Gillespie. Now I'm gonna fire this away. Roast. No. Nothing found.\u003C/p>\u003Cp>Well, that's a little concerning. GitHub username equals body. Read the body. We have fetched the user's GitHub username. Let's just console log the username.\u003C/p>\u003Cp>API dot GitHub users. I don't see the actual username coming back. GitHub. That's always fun. Alright.\u003C/p>\u003Cp>What did I do wrong? GitHub underscore username. Okay. Oh, duh. Are we actually passing that in the body of the form?\u003C/p>\u003Cp>Form. Console dot log response. Request payload. API slash roast dot post. What are we getting back here?\u003C/p>\u003Cp>GitHub username form dot GitHub username. Oh, that's right. We are missing a state variable here. So is it actually submitting the form? No?\u003C/p>\u003Cp>No. Okay, friends. What do we do from here? We have handle submit. We're going to use fetch await.\u003C/p>\u003Cp>Wait that fetch request. We should have already got this back. Of course. There it is. What a dumb dumb.\u003C/p>\u003Cp>Forgot to actually fix the v model there. So that's what that is. Sometimes, these are not great to do at the end of the day. But okay. Where we at as far as time?\u003C/p>\u003Cp>We've got forty two minutes remaining. I feel pretty confident on this one. Alright. Now with our roast, we can remove this. We should be able to get that information.\u003C/p>\u003Cp>Now let's make sure that we're getting what we want back from that API. Great. There's the profile. There's the repos, and there's the profile. Read me.\u003C/p>\u003Cp>Great. Alright. So what are we gonna do with these now? Right? The next step in this process would be to, pass the profile to LLM and ask it to summarize for us.\u003C/p>\u003Cp>What do we want this to return? It should return something that looks like this. We want a letter from Santa. Letter from Santa in HTML. And then we're gonna want the the list, naughty or nice.\u003C/p>\u003Cp>And that should be all we really need to return. Alright. LLM returns JSON. Cool. So what is the LLM we're gonna use?\u003C/p>\u003Cp>You know, typically, I use OpenAI for a lot of the stuff that I do here at Directus. I've been messing around a lot with, Claude locally. So we're just going to try this out. Santa letters. So we're gonna use anthropic.\u003C/p>\u003Cp>There's my API key. We're going to drop that in our ENV file, if I can actually get there. Jeez. There we go. I'm just gonna call it Claude ABI key.\u003C/p>\u003Cp>Okay. Great. And by the time you've watched this, hopefully, I've disabled that key. So, don't stop the video and try to figure that out. Alright.\u003C/p>\u003Cp>Let's pull up our docs for the API. We need to get the API reference. And let's define this prompt. Prompt. You are a letter writing AI.\u003C/p>\u003Cp>Alright. Analyze the following GitHub profile. You are the open source Santa Claus. You determine whose open source contributions are naughty or nice, analyze the following GitHub profile, Return a JSON object with the following fields, a letter from Santa and HTML. Set a really high bar for the nice list.\u003C/p>\u003Cp>What else do we need as far as a prompt? And, yeah, here is the data, profile, readme, json, stringify. Wonder why it's doing that. But okay. Nevertheless, there we go.\u003C/p>\u003Cp>Turn a JSON object instead of really write the letter in a snarky sarcastic tone. Cool. Alright. And now we're going to send that to Anthropic. Alright.\u003C/p>\u003Cp>So if we look at their oh, looks like we could just use their JavaScript SDK. That's great. Let's go ahead and open this up. We'll fire that up, install this thing. Import anthropic.\u003C/p>\u003Cp>Great. And then we're going to create that message. Alright. Constant AI response equals anthropic messages dot create Claude Sonnet. Okay.\u003C/p>\u003Cp>Messages user role, content prompt. Do we wanna set, like, max tokens? What is the what's the default for max tokens? Where do we actually pass this API key? Getting started authentication, x API key.\u003C/p>\u003Cp>API key equals process e n v. And, again, like, you could start to see why I really like using cursor because it has, like, this sixth sense for a lot of this stuff that I'm actually trying to do. Sometimes it gets that wrong, but a lot of times it gets it right. So alright, AI response messages. Do we wanna set a max tokens?\u003C/p>\u003Cp>Body messages, max tokens required. Let's give some more parameters. Write a short letter in a short in a snarky sarcastic tone. That is 500 words or less. And then for the tokens, if we look at Sonnet, we've got like a context window of like 200,000, so maybe a hundred thousand tokens.\u003C/p>\u003Cp>Oh, no. That's the output. Max output is eight nine one two. That's fine. Max tokens.\u003C/p>\u003Cp>Great. And let's return. Actually, what we're gonna do next is save that to the Directus database. Right? So we've got this collection for our profile.\u003C/p>\u003Cp>What I've also done, I've got a utility set up here. This is just using the Directus SDK. And one of the nice things about Nuxt, I say that a lot, is, the ability to it will auto import this for me. So I don't have to import it. I should just be able to call Directus server right here.\u003C/p>\u003Cp>So let's call it Directus response equals await directus server dot request create item. That's going to be in the profile. And we'll do the GitHub username. That's actually going to be username. Letter response, content dot text.\u003C/p>\u003Cp>I don't actually know what we're gonna get back directly. Return only a JSON object. And maybe we wanna add something like this for let's just do code. We'll set this up. And I'm just gonna add a field for, let's call it metadata or something where I'm just gonna store the entire response.\u003C/p>\u003Cp>And honestly, let's just do that to begin with. Metadata, AI response, content dot text. So if we take a look at the API reference, we go back to messages here. I'm kinda curious as to what we're gonna get back. The content text.\u003C/p>\u003Cp>Okay. Type text something. We'll get back something from the system. Let's just even do it this way. We'll say content direct us response, and then we're going to return direct us response.\u003C/p>\u003Cp>See what that gives us. Now let's go in. Where's our app? We'll switch back to Chrome. I do like Arc.\u003C/p>\u003Cp>I've found it to be lacking for development because it's just not super fast. Alright. So fingers crossed that this actually does what it should do. And, let's make this even nicer. And we'll add, like, a loading state, constant loading, ref equals false.\u003C/p>\u003Cp>We'll add loading dot value equals true. Loading dot value equals false. Great. And what else do we want to do? Is there a loading state on the actual form?\u003C/p>\u003Cp>Let's take a look. So Nuxt UI state, there is not a loading state on that. There should be on the button though. So just update that. Okay.\u003C/p>\u003Cp>And let's test this bad way out. Submit. Alright. We're waiting. We're waiting.\u003C/p>\u003Cp>We're waiting. We're waiting. We're waiting. This could take a minute. So, you know, we might even want to, like, potentially set up a oh, okay.\u003C/p>\u003Cp>So we're not getting anything back. We see a request error. So let's go into our roast, and we should probably do some error handling. Alright. Catch error, console error.\u003C/p>\u003Cp>Return, or we could just throw the error. What do we got here? Format. Alright. Let's refresh.\u003C/p>\u003Cp>I'll try this again and see what kind of error we're getting and why. Pending. Invalid user credentials for Directus. Okay. Great.\u003C/p>\u003Cp>So, just wasting tokens there, throwing them into the void. One of the things that you'll notice, I do have this direct as URL set up, but, my server token is probably a % not correct. So I'm gonna go in and create a token for this. We'll just create a new token. We'll call this the server token.\u003C/p>\u003Cp>And I wanna make sure in my utility that I have that set as server token, direct us URL. Okay. Let's try this thing again. PPM dev. I will restart the dev server, pull in that new ENV, though I think Nuxt may automatically update that for us.\u003C/p>\u003Cp>How we doing on time? We got twenty nine minutes left, so I'm feeling pretty confident that we can get something out of this. Let's go ahead and try it again. Bryant Gillespie. Submit.\u003C/p>\u003Cp>K. K. Roast. You do not have permission to access this. Okay.\u003C/p>\u003Cp>Can anybody spot the error? It is because I left off a s. We have profiles, and this is profile. So again, if I I don't know if I you can actually see the logs for anthropic. Okay.\u003C/p>\u003Cp>Yeah. We could see here's the actual logs. It's probably not showing what we've got there. But anyway alright. We'll try this one more time.\u003C/p>\u003Cp>Let's just clean this up a bit. And away we go. Dun dun dun. I don't like the looks of this, actually. Let's just reset.\u003C/p>\u003Cp>Try this again. AI response. We got the prompt. Got the profile. Dun dun dun.\u003C/p>\u003Cp>The moment of truth. Are we actually gonna be able to get this thing to work? Brig Gillespie. Submit. Obviously, this would probably be better as, like, a background job or something like that.\u003C/p>\u003Cp>Alright. So we refresh, and we have something here. Okay. Yeah. So we're getting some text back.\u003C/p>\u003Cp>It looks like we need to parse the JSON. The letter is going to be text parsed response, text, parse response dot list, and then we get metadata, which would just be the parse response, I'm assuming. Alright. We're gonna delete this out. Let's run this again.\u003C/p>\u003Cp>And hopefully I'm not burning through all these credits that I loaded up. Okay. So now we're looking great. Okay. So we have our username.\u003C/p>\u003Cp>We've got our letter. Ho ho ho. What do we have here? Another developer thinking they can impress Santa with a few measly repositories. I've seen l's with more impressive profiles.\u003C/p>\u003Cp>I got made it to the naughty list. Great. Amazing. Alright. So now that's working as intended.\u003C/p>\u003Cp>Let's let's make this pretty. Right? The form, we're going to do max width. Maybe we set this to Excel. Move that form to the somewhat in the center of the page.\u003C/p>\u003Cp>And let's just lean on AI here. Right? This is already pretty cool. One of the other things I wanna do is maybe we set up a route where we actually surface this letter. Right?\u003C/p>\u003Cp>So if we do let's do letters as a directory inside pages. And we're gonna do the username in brackets. So just take this username, make that in brackets, and then I'm gonna put letters inside here, and we'll change the name of this to the index route. Alright. So let's just clean this up a bit, wrap this up, and console the error loading.\u003C/p>\u003Cp>Actually, we could do that in finally. Great. And what we're gonna do, if the response is good, we could navigate to the username page. Cool. And that way, you know, basically, like, this could get very expensive if if you made this thing public.\u003C/p>\u003Cp>Right? You don't want people generating like 35 letters to Santa. So we can add a check to the database if we've already got that GitHub username and just return the letter that we we already have. Right? Okay.\u003C/p>\u003Cp>So on the response, as long as there's no error, we're going to navigate dot to form. Github username. And this would be await. Navigate to. Cool.\u003C/p>\u003Cp>Alright. Now let's just lean on AI and see what we could do. Add some Christmas theming to this. Let's see what this actually will do. Add some Christmas thinging, ho ho ho.\u003C/p>\u003Cp>Looks like it's generating some random messages. Code review letters to Santa, random message, decorative elements. Great. Love decorative elements. Now, with cursor, I'm just gonna click apply here.\u003C/p>\u003Cp>It should go through and run through this actual code. I can close this out and see, you know, in kind of a preview way what it's gonna change. And if we hit reload oh, what we got going on here? Letters index. Is that because I changed the route?\u003C/p>\u003Cp>Okay. Yeah. Now we're looking very festive here. This looks this looks great. AI, what can you do?\u003C/p>\u003Cp>Alright. The other thing I see, maybe we want this to be block. Will that get it done? Block. Class.\u003C/p>\u003Cp>Let's just make the width full. Width full. Okay. And then let's shrink this actual form a bit. Yeah.\u003C/p>\u003Cp>MD. There we go. Alright. We're deep in the Christmas cheer now. And, while we wait, let's well, not while we wait.\u003C/p>\u003Cp>Let's actually go in and now we're gonna work on this letter. Alright. So, this does have a Nux plug in. This is just my boilerplate where I can go in and actually request the information from Directus on the client side, or, you know, I could set up a route for this on the server side in Nuxt. Both of those ways are are totally valid depending on your application.\u003C/p>\u003Cp>Obviously, totally up to you. We will just, let's let's keep it the same theme. We're going to, like, fetch roast, or we could do, like, a roast.git.ts. And what are we gonna pass? Do we want to pass the username as a param, or we'll just pass it as a query parameter?\u003C/p>\u003Cp>Okay. So in this one, what we're gonna do, we will call the profiles endpoint inside Directus. So we'll just go const, response equals await Directus server. And, you know, sometimes you wanna make requests on the server side. That's why I've got this set up, this way.\u003C/p>\u003Cp>We're gonna do read item, and I gave this a UUID. We could've used the actual profile as the primary key. But, what we're gonna do, read profiles, and we're gonna set up a query for this. So we'll do a filter parameter, the username. So that's the field.\u003C/p>\u003Cp>We're gonna drop down again. This will be equal to the username. So first we're gonna have to get the username equals get router param. Nope. We're gonna do git query, and that would just be the query.\u003C/p>\u003Cp>Great. Username. We could destructure this if we wanted to. Return username equals username, and we're gonna return that response. Great.\u003C/p>\u003Cp>Cool. So now we do this. And on this one, what we can do is use the use fetch composable from Nuxt. So this will be we've got some data. We're gonna use fetch, and we're gonna call API slash roast.\u003C/p>\u003Cp>And the is it params? I believe. See what we got. And let's add the so the same festivities, I guess. Festiveness.\u003C/p>\u003Cp>Perfect. Alright. Decorative elements, blah blah blah, random messages. We're gonna put that up here in the script. Okay.\u003C/p>\u003Cp>Code review letters to Santa. And instead of the form, right, we're gonna replace this with data. Alright. So now if I do this, what's gonna happen? Route is not defined.\u003C/p>\u003Cp>Okay. So we just need to call use route to fetch that route. And do we actually get the stuff that we need here? We could test this API as well. Letters API roast username equals Brian Gillespie.\u003C/p>\u003Cp>Okay. Yeah. So that's getting us what we want from direct us except is it query? What is the use fetch? This is where, like, Nuxt documentation comes in handy.\u003C/p>\u003Cp>Use fetch. Where we at? We got sixteen minutes remaining. We got data use fetch. What are the URL query?\u003C/p>\u003Cp>Okay. Alias for query. That's what I thought. Root params username. Oh, data.\u003C/p>\u003Cp>Are we actually let's jump into the view dev tools. We'll hit the username route. And I see the data here. Here's the issue. Right?\u003C/p>\u003Cp>It is returning an array. So inside our routes, we could, you know, do something like this where we're just picking off the first item. I could also do that transform that on the the Nuxt side if I wanted to. Here's our letter from Santa. Cool.\u003C/p>\u003Cp>Code review letters from Santa. What I'm gonna do, let's use the pros class from Tailwind to get styling for this. We'll make the text dark green. Great. That's fine.\u003C/p>\u003Cp>And then the interior of this, we're just going to use v HTML. So we get this. Do I not have Tailwind typography into this? At plug in Tailwind typography. Okay.\u003C/p>\u003Cp>Yeah. So there we go. Now we've got the letter from Santa Claus. This is looking really nice. Perfect.\u003C/p>\u003Cp>Let's add like a cursive font. Right? Font family cursive. And this is Tailwind four, where all the config is basically CSS variables. So, really enjoying that Nuxt module, playing around with it.\u003C/p>\u003Cp>Let's find a handwritten font. Okay. Caveat. Looks nice. Nuxt has also added a a like this font amazing thing where you just throw your fonts in the CSS and it will actually download these things for you.\u003C/p>\u003Cp>So let's take a look at this. Right? I'm just gonna change this to font cursive and bada bing bada boom, we get what we want. So let's put, like, pros XL to XL. And there we go.\u003C/p>\u003Cp>So dear Bryant, what do we have here? Blah blah blah, etcetera. We have got thirteen minutes left on the clock. What can we do for fun? Let's go back and actually test this thing out.\u003C/p>\u003Cp>I'm just gonna refresh. There we go. I'm gonna do our fearless leader here at Directus, mister Ben Haines. We're gonna send this to Santa, and something bad happened. We could not find okay.\u003C/p>\u003Cp>So it looks like this one is not finding Ben's profile. Haynes, Maine. And Haynes Haynes Haynes Haynes Haynes. Would that be at, like, Master branch maybe? Where's our roast?\u003C/p>\u003Cp>Roast.post, profile read me. Try. I bet it's at master. I'm just gonna do this the quick and dirty way. Alright.\u003C/p>\u003Cp>So we go back. Let's try this again. Mister Ben Haines, we're about to roast you, sir. Alright. So we're checking the list twice.\u003C/p>\u003Cp>And eleven minutes on the clock. We've got the letter to Ben Haines from Ben Haines. Why are we not seeing the actual letter? There it is. I'm dreaming of a Nuxt application that actually works.\u003C/p>\u003Cp>What is going on with this? Letters, username, data dot name. I'm assuming because there is no name. Username. I'm running this on a sour note here.\u003C/p>\u003Cp>Ben Haines. That's kinda weird. Ben Haines. Why is it doing that? API roast username.\u003C/p>\u003Cp>What is going on here? Get async data. API roast. Why does it work for me and not for mister Haynes? What are we actually doing wrong here?\u003C/p>\u003Cp>Did I spell the name wrong? GitHub username. Alright. E pipe. Use fetch roast.\u003C/p>\u003Cp>Can't find the username? Profiles get username, get query. Is it read query? No. It's get query.\u003C/p>\u003Cp>Return query. API API roast. Ben Haines. So why aren't we why isn't this working? So it's not actually finding the username for that, which is odd because I have the username right there.\u003C/p>\u003Cp>Filter contains. Okay. I don't understand it, but we're gonna roll with it. Great. Some type of encoding or something maybe.\u003C/p>\u003Cp>Not sure. Booyah. Ben, I'm gonna read this to you. Dear Ben, ho ho ho. Well, isn't this embarrassing?\u003C/p>\u003Cp>I've been reviewing your GitHub profile, and I must say I'm thoroughly underwhelmed. 20 whole repositories, you must have been super busy this century. Meanwhile, Santa's got billions of believers worldwide. Look, I'm not saying you're on the naughty list because your contributions are lackluster. I'm saying if you were open source for coal, you barely have enough to heat a dollhouse.\u003C/p>\u003Cp>That is brutal. So let's call that a win. This is AI letters with Santa. Do we wanna do one more just for fun? Just for giggles?\u003C/p>\u003Cp>Let's let's test this out. Directus Directus. I forget Reich's actual, GitHub profile. There it is. Okay.\u003C/p>\u003Cp>So we're gonna throw mister Reich Van Zanten in there, our CTO, see what comes out of this thing. Hopefully, we got everything we need. It will do its thing. And and, that's not gonna pick on Wrike. Yeah.\u003C/p>\u003Cp>I don't know what's going on with this thing. Potentially some kind of caching issue. Don't know. Anyway, response zero. Down to the wire, five minutes, four minutes, three minutes, two minutes, no minutes.\u003C/p>\u003Cp>Is the server running? Use async data. And what if we just use fetch? Response. It's gonna be response dot letter.\u003C/p>\u003Cp>Oh, boy. Response fetch, browse, params, username. Come on. Failed to stringify the server logs. What is going on?\u003C/p>\u003Cp>This feels like a crappy way to end this one. It should be, like, festive with holiday cheer. I don't understand what is going on with Nox. Oh, duh, dummy. You have to wait the promise.\u003C/p>\u003Cp>Is that getting us what we need? Still not getting us what we need. Data is not defined on the instance. Where else am I getting the data at? Fested messages, data.\u003C/p>\u003Cp>Oh, if response. We're not even getting a response. SSR, undefined, undefined. Hey. That's the way the cookie crumbles sometimes.\u003C/p>\u003Cp>I'm not sure what I am doing wrong with this. I'm sure it'll come to me right after I get done with this. Is it like a key? Cash. Cash, no cash.\u003C/p>\u003Cp>Cache. No cache. No cache. Well, at least Ben's works. At least mine works.\u003C/p>\u003Cp>Not entirely sure what's going on with this little API that I've written, why it is caching this. But, hey, that's AI letters to Santa. That's the way it goes. This has been a hundred apps, hundred hours. Thanks for joining me.\u003C/p>\u003Cp>We'll catch you on the next episode. See you.\u003C/p>","Alright. Alright. Alright. We are back with the Christmas edition of 100 apps, one hundred hours. Today, we are going to be building AI letters from Santa. I've got my lumberjack style on today. My wife called this lumberjack Jesus earlier, but I digress. We're back for more. The rules of 100 apps, one hundred hours. If this is your first show, we have sixty minutes to plan and build an application, a website, a portal, whatever. Whatever we're building, sixty minutes, no more, no less. And rule number two, the anti rule, use whatever you have at your disposal. And since this is an AI Christmas special, I'm gonna pull out all the stops. So let's dive right in. We're gonna hit the clock here. Fire it up. Sixty minutes on the timer. Go. Alright. So AI letters from Santa. What do we actually want out of this? So I have to admit, I cheated a little bit because I thought about this with my team, and I knew we wanted to do this. I've seen things in the past where you write a letter to Santa, you get something back in the mail, etcetera, etcetera. With AI, we could take this up a notch. So combining two ideas. A while back, I saw a GitHub roast page where you enter in your GitHub profile and it, basically will scrape that and give you a roast of how well you're actually not doing in GitHub. So we're gonna combine that with a letter from Santa. And basically, what we wanna do is, enter a GitHub profile. We're gonna scrape that profile. We're going to send that to AI. So we want LLM analysis of the profile. I'm not sure what we're gonna call that. And then I'm going to bucket people on the open source naughty or nice list. So score naughty or nice list. And then we're gonna generate a letter from open source Santa. Generate a letter from open source Santa to that GitHub profile to that profile. Alright. So as far as that functionality, this looks pretty good. Right? What are the tools that we're gonna use of the trade today? I've got a Directus Docker container up and running locally. Directus is obviously the back end we're using to store all of these things. And if everything works as intended p m p m g. I guess, sometimes things don't work as you intend. I've got a Nuxt application that we are going to try and use here. I'm not sure what's going on, but let's hop into the Directus instance. So I'm just gonna pull up Chrome. We'll log in to 8055, and I should be able to pull up my back end. So great. Got Directus running. You could see this is a pretty blank instance of Directus. This is just the boilerplate I use now. There are a couple extensions installed that I was testing, just messing around with. But, let's make sure. What are we doing here? For MPMI. Sometimes these things never go as planned. Okay. So maybe now we can get this Nuxt application up and running that will be served at local host 3,000, and we'll just basically use it to scaffold out our communications. As far as what I'm using, I've upgraded this boilerplate that I've used for 100 apps to, the Nuxt UI v three alpha, just to play around with Tailwind four and, you know, some of these nice new components that are coming from, like, Radix view. So alright. Let's actually model this thing out. Right? What do we need as far as our data models? I think we just need, like, a maybe like a profiles. So under profiles, we would have, what, our username, letter from Santa, letter from Santa, list, you know, are you naughty or nice? Great. And what else do we need? Let's let's jam on that. So we'll, just set up the back end for this. I'm just gonna create a new collection. We're going to call this profiles is the name of it. And why can't I zoom way in? There we go. That's maybe too far, but all good. Let's do created at, updated at. K. Status sort not needed. Who this was created by, I'm not super concerned with. So now we have a profile. We're gonna do the username. Great. That's where we'll store the GitHub profile. What else do we need? What else did we have here? We've got the letter from Santa. What is that gonna be in letter from let's just call it letter. Great. We'll use the WYSIWYG editor inside Directus so we could just store, I'm assuming, HTML content for that. And then we've got the list. So that's basically gonna be a string. We can, you know, make this look nice inside the directus admin. We'll just give it a naughty. Feel naughty just typing that out, and then we have the nice list. Great. There we go. And I'm just going to go on record that we're probably as soon as we start typing naughty into the AI stuff, we'll probably get some some things back. Like a I probably set off the content alarms or something like that. So there we go. We've got a username. We've got a letter. We've got a list. You know, I could potentially put that in here. What I'm gonna do now, I'm just gonna let's just we're just using Directus to store this at the moment. Right? There's a lot of different ways I could go with how to actually generate the application here. But Directus allows me to create custom extensions. What I'm gonna do here is just start, working on this from the Nuxt side of it. So we're gonna input the, actual form here. Let's add a profile. What are we gonna call this? Let's just call this letters dot view. We'll get a view component set up. Lang equals TS. Great. I need to work on my little macros here. Okay. Alright. The other thing that you'll notice here is that I am using cursor. So cursor I recently started testing this thing out. Really enjoying the actual auto completions for this thing. So, I don't have it usually generate like a a giant list of code, but the automations, or the auto completions are are pretty nice for this thing. So let's start with, what, step one. It'd be enter GitHub profile info. GitHub username. Alright. So the only thing here, sometimes it gets a little wonky with the okay. So we use the you form from Nuxt UI. Good question, Brian. The new one, the alpha, they changed some of the conventions. So I've got a form with a schema. I've got a form field instead of a form group, and then I've got an input. Okay. So we've got the form, new form field, and input GitHub username. Let's just see what that gets us on the front end. We're gonna go to this page, which is letters. Okay. Alright. So let's go ahead and just center this up. I think there's actually a container component we can use. Great. Cool. Okay. So now we have a GitHub username, and let's add a submit button. New button, click handle submit, and boom. We have a GitHub username, blah blah blah. Hit submit. Supposedly does something. What it's gonna do right now? Absolutely nothing. Alright. So the next thing that we wanna do, let's kick this thing off. We want to have the form state, we use reactive for that. Great. GitHub username. Okay. And then we're gonna write a function to handle submit. Thank you. Yeah. Great. We'll just, console log that. Right? Boom. There we go. We can see the GitHub username, yada yada yada. Alright. This is actually gonna be an async function. Great. Okay. So now what do we wanna do with this? Right? We have to think about our application structure. And what I'm gonna do here is just basically add a Nuxt server route. So if we break this down, in this server route, what we're gonna do, call the GitHub API, call GitHub API. We're gonna wanna grab a couple pieces of information like the user profile, or any other repos, and maybe, like, their their public read me. I guess we could loop through the actual repos and, you know, pick up more information there, but, let's see what we can get done with that piece. Alright. So let's just go here. We're gonna set up a new route. Let's call it, roast route. We'll do post, and just gonna copy the event handler here. K. So now whenever we hit this route with a post, it should return hello world. We can just check and see if that's actually gonna work. So, we will do the I'm trying to think if that's gonna return. Nope. So we got the response. We're gonna do await. We can use the regular fetch or the dollar sign fetch, which is the Nuxt specific version. And just test this out, see what we get back in the console. Where are you? Okay. Yeah. So we can see the request going out. We can see hello world coming back. Great. Cool. Alright. So now what we're gonna do, right, let's just scaffold this out. We are going to pick up the body. There's a wait read body. Great. So that is going to have the GitHub username in there. And, how did we spell that? Yep. Great. Alright. So we're gonna say GitHub username, and then we've got, like, this git roast function. I'm not really sure where some of these auto completions are coming from. But, what we're gonna do next, let's call yep. There we go. That's a good one. API users, GitHub username. Is that the correct one? Let's just test that. All the developers on my team are screaming and crying at the moment, watching all these AI auto completions. So that seems fair. Great. And let's actually use the Nuxt equivalent. Just this is using OFETCH, which does some automated data transformation and should automatically throw errors for you as well, which is nice. So this is gonna be the let's call this a profile. Alright. And then if we take a look at the profile, we probably wanna get the actual repos for that user as well. Alright. So we'll get the repos. Great. And let's just take a look at the data we're getting from the actual repos. Okay. So what do we actually concern ourself with here? Do we actually want all of this information? What do we actually care about from these? So stargazers, watchers counts, maybe those properties. You know what? Let's just jam it all in there and see what comes out of it. Right? And then let's get the profile readme. GitHub user content, GitHub username, repos dot name, Repos dot name. No. That's not gonna cut it. I think it's gonna be, what, GitHub username. GitHub username. Somebody who's already done this before. Give me the structure. And then main. Let's just see if we can find that. Read me will just populate my name. I don't even know if I have a actually have a read me. Yeah. There we go. Okay. So that is the structure. Great. That is what we needed to confirm. And now let's just actually return this and see what we'll get back. Alright. Brian Gillespie. Now I'm gonna fire this away. Roast. No. Nothing found. Well, that's a little concerning. GitHub username equals body. Read the body. We have fetched the user's GitHub username. Let's just console log the username. API dot GitHub users. I don't see the actual username coming back. GitHub. That's always fun. Alright. What did I do wrong? GitHub underscore username. Okay. Oh, duh. Are we actually passing that in the body of the form? Form. Console dot log response. Request payload. API slash roast dot post. What are we getting back here? GitHub username form dot GitHub username. Oh, that's right. We are missing a state variable here. So is it actually submitting the form? No? No. Okay, friends. What do we do from here? We have handle submit. We're going to use fetch await. Wait that fetch request. We should have already got this back. Of course. There it is. What a dumb dumb. Forgot to actually fix the v model there. So that's what that is. Sometimes, these are not great to do at the end of the day. But okay. Where we at as far as time? We've got forty two minutes remaining. I feel pretty confident on this one. Alright. Now with our roast, we can remove this. We should be able to get that information. Now let's make sure that we're getting what we want back from that API. Great. There's the profile. There's the repos, and there's the profile. Read me. Great. Alright. So what are we gonna do with these now? Right? The next step in this process would be to, pass the profile to LLM and ask it to summarize for us. What do we want this to return? It should return something that looks like this. We want a letter from Santa. Letter from Santa in HTML. And then we're gonna want the the list, naughty or nice. And that should be all we really need to return. Alright. LLM returns JSON. Cool. So what is the LLM we're gonna use? You know, typically, I use OpenAI for a lot of the stuff that I do here at Directus. I've been messing around a lot with, Claude locally. So we're just going to try this out. Santa letters. So we're gonna use anthropic. There's my API key. We're going to drop that in our ENV file, if I can actually get there. Jeez. There we go. I'm just gonna call it Claude ABI key. Okay. Great. And by the time you've watched this, hopefully, I've disabled that key. So, don't stop the video and try to figure that out. Alright. Let's pull up our docs for the API. We need to get the API reference. And let's define this prompt. Prompt. You are a letter writing AI. Alright. Analyze the following GitHub profile. You are the open source Santa Claus. You determine whose open source contributions are naughty or nice, analyze the following GitHub profile, Return a JSON object with the following fields, a letter from Santa and HTML. Set a really high bar for the nice list. What else do we need as far as a prompt? And, yeah, here is the data, profile, readme, json, stringify. Wonder why it's doing that. But okay. Nevertheless, there we go. Turn a JSON object instead of really write the letter in a snarky sarcastic tone. Cool. Alright. And now we're going to send that to Anthropic. Alright. So if we look at their oh, looks like we could just use their JavaScript SDK. That's great. Let's go ahead and open this up. We'll fire that up, install this thing. Import anthropic. Great. And then we're going to create that message. Alright. Constant AI response equals anthropic messages dot create Claude Sonnet. Okay. Messages user role, content prompt. Do we wanna set, like, max tokens? What is the what's the default for max tokens? Where do we actually pass this API key? Getting started authentication, x API key. API key equals process e n v. And, again, like, you could start to see why I really like using cursor because it has, like, this sixth sense for a lot of this stuff that I'm actually trying to do. Sometimes it gets that wrong, but a lot of times it gets it right. So alright, AI response messages. Do we wanna set a max tokens? Body messages, max tokens required. Let's give some more parameters. Write a short letter in a short in a snarky sarcastic tone. That is 500 words or less. And then for the tokens, if we look at Sonnet, we've got like a context window of like 200,000, so maybe a hundred thousand tokens. Oh, no. That's the output. Max output is eight nine one two. That's fine. Max tokens. Great. And let's return. Actually, what we're gonna do next is save that to the Directus database. Right? So we've got this collection for our profile. What I've also done, I've got a utility set up here. This is just using the Directus SDK. And one of the nice things about Nuxt, I say that a lot, is, the ability to it will auto import this for me. So I don't have to import it. I should just be able to call Directus server right here. So let's call it Directus response equals await directus server dot request create item. That's going to be in the profile. And we'll do the GitHub username. That's actually going to be username. Letter response, content dot text. I don't actually know what we're gonna get back directly. Return only a JSON object. And maybe we wanna add something like this for let's just do code. We'll set this up. And I'm just gonna add a field for, let's call it metadata or something where I'm just gonna store the entire response. And honestly, let's just do that to begin with. Metadata, AI response, content dot text. So if we take a look at the API reference, we go back to messages here. I'm kinda curious as to what we're gonna get back. The content text. Okay. Type text something. We'll get back something from the system. Let's just even do it this way. We'll say content direct us response, and then we're going to return direct us response. See what that gives us. Now let's go in. Where's our app? We'll switch back to Chrome. I do like Arc. I've found it to be lacking for development because it's just not super fast. Alright. So fingers crossed that this actually does what it should do. And, let's make this even nicer. And we'll add, like, a loading state, constant loading, ref equals false. We'll add loading dot value equals true. Loading dot value equals false. Great. And what else do we want to do? Is there a loading state on the actual form? Let's take a look. So Nuxt UI state, there is not a loading state on that. There should be on the button though. So just update that. Okay. And let's test this bad way out. Submit. Alright. We're waiting. We're waiting. We're waiting. We're waiting. We're waiting. This could take a minute. So, you know, we might even want to, like, potentially set up a oh, okay. So we're not getting anything back. We see a request error. So let's go into our roast, and we should probably do some error handling. Alright. Catch error, console error. Return, or we could just throw the error. What do we got here? Format. Alright. Let's refresh. I'll try this again and see what kind of error we're getting and why. Pending. Invalid user credentials for Directus. Okay. Great. So, just wasting tokens there, throwing them into the void. One of the things that you'll notice, I do have this direct as URL set up, but, my server token is probably a % not correct. So I'm gonna go in and create a token for this. We'll just create a new token. We'll call this the server token. And I wanna make sure in my utility that I have that set as server token, direct us URL. Okay. Let's try this thing again. PPM dev. I will restart the dev server, pull in that new ENV, though I think Nuxt may automatically update that for us. How we doing on time? We got twenty nine minutes left, so I'm feeling pretty confident that we can get something out of this. Let's go ahead and try it again. Bryant Gillespie. Submit. K. K. Roast. You do not have permission to access this. Okay. Can anybody spot the error? It is because I left off a s. We have profiles, and this is profile. So again, if I I don't know if I you can actually see the logs for anthropic. Okay. Yeah. We could see here's the actual logs. It's probably not showing what we've got there. But anyway alright. We'll try this one more time. Let's just clean this up a bit. And away we go. Dun dun dun. I don't like the looks of this, actually. Let's just reset. Try this again. AI response. We got the prompt. Got the profile. Dun dun dun. The moment of truth. Are we actually gonna be able to get this thing to work? Brig Gillespie. Submit. Obviously, this would probably be better as, like, a background job or something like that. Alright. So we refresh, and we have something here. Okay. Yeah. So we're getting some text back. It looks like we need to parse the JSON. The letter is going to be text parsed response, text, parse response dot list, and then we get metadata, which would just be the parse response, I'm assuming. Alright. We're gonna delete this out. Let's run this again. And hopefully I'm not burning through all these credits that I loaded up. Okay. So now we're looking great. Okay. So we have our username. We've got our letter. Ho ho ho. What do we have here? Another developer thinking they can impress Santa with a few measly repositories. I've seen l's with more impressive profiles. I got made it to the naughty list. Great. Amazing. Alright. So now that's working as intended. Let's let's make this pretty. Right? The form, we're going to do max width. Maybe we set this to Excel. Move that form to the somewhat in the center of the page. And let's just lean on AI here. Right? This is already pretty cool. One of the other things I wanna do is maybe we set up a route where we actually surface this letter. Right? So if we do let's do letters as a directory inside pages. And we're gonna do the username in brackets. So just take this username, make that in brackets, and then I'm gonna put letters inside here, and we'll change the name of this to the index route. Alright. So let's just clean this up a bit, wrap this up, and console the error loading. Actually, we could do that in finally. Great. And what we're gonna do, if the response is good, we could navigate to the username page. Cool. And that way, you know, basically, like, this could get very expensive if if you made this thing public. Right? You don't want people generating like 35 letters to Santa. So we can add a check to the database if we've already got that GitHub username and just return the letter that we we already have. Right? Okay. So on the response, as long as there's no error, we're going to navigate dot to form. Github username. And this would be await. Navigate to. Cool. Alright. Now let's just lean on AI and see what we could do. Add some Christmas theming to this. Let's see what this actually will do. Add some Christmas thinging, ho ho ho. Looks like it's generating some random messages. Code review letters to Santa, random message, decorative elements. Great. Love decorative elements. Now, with cursor, I'm just gonna click apply here. It should go through and run through this actual code. I can close this out and see, you know, in kind of a preview way what it's gonna change. And if we hit reload oh, what we got going on here? Letters index. Is that because I changed the route? Okay. Yeah. Now we're looking very festive here. This looks this looks great. AI, what can you do? Alright. The other thing I see, maybe we want this to be block. Will that get it done? Block. Class. Let's just make the width full. Width full. Okay. And then let's shrink this actual form a bit. Yeah. MD. There we go. Alright. We're deep in the Christmas cheer now. And, while we wait, let's well, not while we wait. Let's actually go in and now we're gonna work on this letter. Alright. So, this does have a Nux plug in. This is just my boilerplate where I can go in and actually request the information from Directus on the client side, or, you know, I could set up a route for this on the server side in Nuxt. Both of those ways are are totally valid depending on your application. Obviously, totally up to you. We will just, let's let's keep it the same theme. We're going to, like, fetch roast, or we could do, like, a roast.git.ts. And what are we gonna pass? Do we want to pass the username as a param, or we'll just pass it as a query parameter? Okay. So in this one, what we're gonna do, we will call the profiles endpoint inside Directus. So we'll just go const, response equals await Directus server. And, you know, sometimes you wanna make requests on the server side. That's why I've got this set up, this way. We're gonna do read item, and I gave this a UUID. We could've used the actual profile as the primary key. But, what we're gonna do, read profiles, and we're gonna set up a query for this. So we'll do a filter parameter, the username. So that's the field. We're gonna drop down again. This will be equal to the username. So first we're gonna have to get the username equals get router param. Nope. We're gonna do git query, and that would just be the query. Great. Username. We could destructure this if we wanted to. Return username equals username, and we're gonna return that response. Great. Cool. So now we do this. And on this one, what we can do is use the use fetch composable from Nuxt. So this will be we've got some data. We're gonna use fetch, and we're gonna call API slash roast. And the is it params? I believe. See what we got. And let's add the so the same festivities, I guess. Festiveness. Perfect. Alright. Decorative elements, blah blah blah, random messages. We're gonna put that up here in the script. Okay. Code review letters to Santa. And instead of the form, right, we're gonna replace this with data. Alright. So now if I do this, what's gonna happen? Route is not defined. Okay. So we just need to call use route to fetch that route. And do we actually get the stuff that we need here? We could test this API as well. Letters API roast username equals Brian Gillespie. Okay. Yeah. So that's getting us what we want from direct us except is it query? What is the use fetch? This is where, like, Nuxt documentation comes in handy. Use fetch. Where we at? We got sixteen minutes remaining. We got data use fetch. What are the URL query? Okay. Alias for query. That's what I thought. Root params username. Oh, data. Are we actually let's jump into the view dev tools. We'll hit the username route. And I see the data here. Here's the issue. Right? It is returning an array. So inside our routes, we could, you know, do something like this where we're just picking off the first item. I could also do that transform that on the the Nuxt side if I wanted to. Here's our letter from Santa. Cool. Code review letters from Santa. What I'm gonna do, let's use the pros class from Tailwind to get styling for this. We'll make the text dark green. Great. That's fine. And then the interior of this, we're just going to use v HTML. So we get this. Do I not have Tailwind typography into this? At plug in Tailwind typography. Okay. Yeah. So there we go. Now we've got the letter from Santa Claus. This is looking really nice. Perfect. Let's add like a cursive font. Right? Font family cursive. And this is Tailwind four, where all the config is basically CSS variables. So, really enjoying that Nuxt module, playing around with it. Let's find a handwritten font. Okay. Caveat. Looks nice. Nuxt has also added a a like this font amazing thing where you just throw your fonts in the CSS and it will actually download these things for you. So let's take a look at this. Right? I'm just gonna change this to font cursive and bada bing bada boom, we get what we want. So let's put, like, pros XL to XL. And there we go. So dear Bryant, what do we have here? Blah blah blah, etcetera. We have got thirteen minutes left on the clock. What can we do for fun? Let's go back and actually test this thing out. I'm just gonna refresh. There we go. I'm gonna do our fearless leader here at Directus, mister Ben Haines. We're gonna send this to Santa, and something bad happened. We could not find okay. So it looks like this one is not finding Ben's profile. Haynes, Maine. And Haynes Haynes Haynes Haynes Haynes. Would that be at, like, Master branch maybe? Where's our roast? Roast.post, profile read me. Try. I bet it's at master. I'm just gonna do this the quick and dirty way. Alright. So we go back. Let's try this again. Mister Ben Haines, we're about to roast you, sir. Alright. So we're checking the list twice. And eleven minutes on the clock. We've got the letter to Ben Haines from Ben Haines. Why are we not seeing the actual letter? There it is. I'm dreaming of a Nuxt application that actually works. What is going on with this? Letters, username, data dot name. I'm assuming because there is no name. Username. I'm running this on a sour note here. Ben Haines. That's kinda weird. Ben Haines. Why is it doing that? API roast username. What is going on here? Get async data. API roast. Why does it work for me and not for mister Haynes? What are we actually doing wrong here? Did I spell the name wrong? GitHub username. Alright. E pipe. Use fetch roast. Can't find the username? Profiles get username, get query. Is it read query? No. It's get query. Return query. API API roast. Ben Haines. So why aren't we why isn't this working? So it's not actually finding the username for that, which is odd because I have the username right there. Filter contains. Okay. I don't understand it, but we're gonna roll with it. Great. Some type of encoding or something maybe. Not sure. Booyah. Ben, I'm gonna read this to you. Dear Ben, ho ho ho. Well, isn't this embarrassing? I've been reviewing your GitHub profile, and I must say I'm thoroughly underwhelmed. 20 whole repositories, you must have been super busy this century. Meanwhile, Santa's got billions of believers worldwide. Look, I'm not saying you're on the naughty list because your contributions are lackluster. I'm saying if you were open source for coal, you barely have enough to heat a dollhouse. That is brutal. So let's call that a win. This is AI letters with Santa. Do we wanna do one more just for fun? Just for giggles? Let's let's test this out. Directus Directus. I forget Reich's actual, GitHub profile. There it is. Okay. So we're gonna throw mister Reich Van Zanten in there, our CTO, see what comes out of this thing. Hopefully, we got everything we need. It will do its thing. And and, that's not gonna pick on Wrike. Yeah. I don't know what's going on with this thing. Potentially some kind of caching issue. Don't know. Anyway, response zero. Down to the wire, five minutes, four minutes, three minutes, two minutes, no minutes. Is the server running? Use async data. And what if we just use fetch? Response. It's gonna be response dot letter. Oh, boy. Response fetch, browse, params, username. Come on. Failed to stringify the server logs. What is going on? This feels like a crappy way to end this one. It should be, like, festive with holiday cheer. I don't understand what is going on with Nox. Oh, duh, dummy. You have to wait the promise. Is that getting us what we need? Still not getting us what we need. Data is not defined on the instance. Where else am I getting the data at? Fested messages, data. Oh, if response. We're not even getting a response. SSR, undefined, undefined. Hey. That's the way the cookie crumbles sometimes. I'm not sure what I am doing wrong with this. I'm sure it'll come to me right after I get done with this. Is it like a key? Cash. Cash, no cash. Cache. No cache. No cache. Well, at least Ben's works. At least mine works. Not entirely sure what's going on with this little API that I've written, why it is caching this. But, hey, that's AI letters to Santa. That's the way it goes. This has been a hundred apps, hundred hours. Thanks for joining me. We'll catch you on the next episode. See you.","ec46771b-85fb-4967-9a7e-81102f96cf74",[184],"cc9262db-1ae2-4bc1-a1f9-7d2fc51a396d",[],{"reps":187},[188,244],{"name":189,"sdr":8,"link":190,"countries":191,"states":193},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[192],"United States",[194,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],"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":245,"link":246,"countries":247},"Michelle Riber","https://meetings.hubspot.com/mriber",[248,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,225,436,437],"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",1773850444259]