[{"data":1,"prerenderedAt":438},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-realtime-leaderboard":121,"100-apps-100-hours-realtime-leaderboard-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},"aad8d674-2b58-4604-8e43-b98f7c6e05cb","realtime-leaderboard","936355804","Bryant races against the clock to build a real-time leaderboard for the Directus Arcade game – Duckin' Cold Emails. Watch as he builds features to submit and display high scores for the game using Directus Realtime and websockets.","b05a9814-7065-4816-841c-74e263ce5b20",65,4,"2024-05-10","Mission: Realtime Leaderboard","\u003Cp>Speaker 0: Hi. Welcome back to another episode of 100 Apps, 100 Hours. I'm your host, Brian Gillespie, and today we are building something that I think is pretty cool. But here on 100 Apps 100 Hours, we build your suggestions, your favorite apps, or rebuild a clone of some other app in 1 hour or less, or publicly fail, get humiliated, trying. It was a lot of stress, to be honest with you.\u003C/p>\u003Cp>So the rules for this, number 1, we have 60 minutes to plan and build an application, no more, no less. And number 2, just use whatever you have at your disposal. So, could be AI, could be past projects, whatever we've got. We're just trying to speed run through applications. And today we've got a cool one.\u003C/p>\u003Cp>We're going to be building a real time leaderboard. So recently, as far as, like April Fools' Day, we put out this amazing game to make you a duckin' pro at cold email. So it's called duckin' cold emails. It's just a runner game where you face off against a sales rep who is sending you cold emails and pop ups on the screen here. So you duck or jump, you get a couple of lives, and at the end of it you can choose to play again, and you get a nice little score.\u003C/p>\u003Cp>So what we're going to do here is create a leaderboard for this, and riggity jig, away we go. Alright. So, 60 minutes to plan and build. Hopefully we don't need all of that, but maybe we will. That's how this works.\u003C/p>\u003Cp>Alright. So I usually like to start by mocking out the functionality that we need out of this. So what do I really want to happen here? When I play, I wanna be able to submit my score and if somebody else is playing around the world, I want that score to and they get a high score, I want that to pop up. I wanna see that.\u003C/p>\u003Cp>So we've got a real time tracking of high scores. This is really large. Let's just trim that down. Real time tracking of high scores, submit a username and message with that, maybe? Maybe we'll make this a little more competitive and that we can talk trash to each other while we do it.\u003C/p>\u003Cp>What else is there really? Display those scores. Display high scores. I'm gonna put maybe sound effects. I'm gonna put that in question mark.\u003C/p>\u003Cp>Maybe that'll be our stretch goal. So that's that's basically the functionality of this. Right? I don't know explicitly how long that's gonna take, but what do we have as far as a data model? That's one of the other things that I like to map out.\u003C/p>\u003Cp>We've got some actual players, but, you know, I'm not going to have a user that needs to log in for this. I'm just going to let we're going to do it NBA Jam style, where you can just key in your initials or something like that. So I'm just thinking here, there's probably only maybe just a single table for this, which is rare for us on a 100 apps to a 100 hours. We're going to have a value for the score, a username, there's gonna be an ID and a time stamp of of when that high score was, and then maybe a message. That seems like it's it to me.\u003C/p>\u003Cp>I don't wanna make this over complicated. Alright. So I'm gonna go into my direct assistance. This is totally blank. We're gonna create a new table.\u003C/p>\u003Cp>We're gonna call it scores. Can I zoom in on this? Yeah. There we go. We'll make it really fancy.\u003C/p>\u003Cp>We'll do the generated UUID. We've got date created. So this is a system field or optional system field that will basically whenever this item gets created it will pre populate some stuff for me. So basically what it does on create of this record in this collection, it will save the current date and time. So we'll unhide this because I do want to see that, And then we'll add a value.\u003C/p>\u003Cp>So as far as the number of emails ducked, I can't duck half an email. So that's gonna be an integer. Looks great. What else do we have? Maybe we'll make that half width.\u003C/p>\u003Cp>And a username. So go sweet handle. Cool. And then we're gonna have a message. So we'll just use the text area for that.\u003C/p>\u003Cp>The type is text, of course, so I could add, like, markdown or something for that if I wanted to, but we'll just keep this short and sweet. Alright. So as far as our data model, is that that's probably it. Right? Looks good.\u003C/p>\u003Cp>Getting on my toes here for this one. Alright. So we got this, specific application. It's basically just a Nuxt single page application. If I look, I've got a single index page, there's an arcade cabinet component that is doing most well, it's not really doing hardly any of the work it's just presentation.\u003C/p>\u003Cp>It gives us this nice arcade cabinet and some text down below. And then we also have oh, these were my buttons I was trying the last time I was in here. And then we have a game component. So the library that we're using for the actual game here is pretty interesting itself. You might go ahead and check it out if you are trying to build a game.\u003C/p>\u003Cp>Did we start the time? Oh, yeah. We did start the timer. Okay. It's called Kaboom JS.\u003C/p>\u003Cp>A pretty interesting library, actually. Makes it really easy to build quick little mini games like this, build stuff that's fun for your users. Alright. So that's the meat and potatoes of it. You know, there's some sprites, there's some functions, there's a couple of different scenes inside the game, but that's what we've got, right?\u003C/p>\u003Cp>So we've got our scores, let's go about setting up our real time connection. So Directus has real time baked into the platform. If I pull up my other project that I'm actually connected to, you can see I have enabled my WebSockets here. And away we go. Alright, so as far as the Knox application, I've just got a simple Directus plug in that provides me with a real time client.\u003C/p>\u003Cp>That's really all we've got. I've set the auth mode to public just so I've got, you know, anybody can connect to this. Again, we're not going to make authentication required here. What I'm also going to do is the ability to read scores and push scores. Okay.\u003C/p>\u003Cp>So what else do we have? I think that's pretty much it. Right? Let's dive in. Let's go to our arcade cabinet.\u003C/p>\u003Cp>Pull this up. Right. This is our wrapper component. I'm not sure where I'm gonna stick the leaderboard. Maybe up here at the top, maybe somewhere down here at the bottom.\u003C/p>\u003Cp>We could just cannibalize this bottom section down here. I'll zoom out in a little bit. But we're gonna get started with the SDK and WebSockets. So we've got a great guide on the documentation for this. It walks you through this in great detail.\u003C/p>\u003Cp>But we're just gonna pull this up. I'm gonna pull that Directus client. Direct cuts. Directus. I can't spell.\u003C/p>\u003Cp>Alright. We're gonna use the Nuxt app. Use Nuxt app composable to get that client. And then what we're gonna do is await directus dot connect. Alright.\u003C/p>\u003Cp>So that will connect and if we keep scrolling down, we can see that we've got a, open function here. You know, if we just wanna log that this was opened. We do this and we go back. And if I look in the console, should be getting a message that this was opened. Switching protocols.\u003C/p>\u003Cp>Not seeing any messages in here for whatever reason. Yeah. I don't see any particular messages, but let's keep moving on. We probably do need this directus dot message handler. Directus dot on web socket dot message console log events.\u003C/p>\u003Cp>What if we just log the entire event? Actually, that'll be a message. Same thing. Let's see what we get here. Do we have that connection established?\u003C/p>\u003Cp>Where is our connection? Why are we not seeing any messages through here? Right? Okay. Let's just add a subscription as well and see what we got.\u003C/p>\u003Cp>Oh, could it be our permission settings? Right? Let's log in and see. Directory's password. Alright.\u003C/p>\u003Cp>So if we look at no. We've got access to that. Alright. We've solved that problem already. Alright.\u003C/p>\u003Cp>So let's just create a subscription and see if we get that. Alright. So we'll go in and do directus dot subscribe, and we're gonna subscribe to the scores, And we're gonna get, we're gonna pass some options to this. So we want the event to be create. The query do we actually need a query?\u003C/p>\u003Cp>I don't think we let's not limit ourselves at this moment. And then maybe I wanna add, like, a UID to this, and we're gonna call this the scores sub. Subscription direct us dot subscribe. And let's see what we got here. Okay.\u003C/p>\u003Cp>So now I can see that particular message coming through. There's our subscription for this. So we're actually getting some data. Let's take a look and see if we open this up in a separate window. So we'll just open scores up.\u003C/p>\u003Cp>We'll create a new scores item, see if we get subscription for that. Yep. Okay. I see some data coming through. Great.\u003C/p>\u003Cp>So we are getting some data for our scores, which is good. Not sure why it's not showing the open function. Not really concerned with that, anyway. Alright. So what we wanna do, just one moment, please.\u003C/p>\u003Cp>We'll stop the clock. Okay. Had a hot call come in. We're restarting the clock. No extra work has been done, and I've totally lost track of where we were at in the process anyway.\u003C/p>\u003Cp>Alright. So, we've got our subscription. We can see that come through. You know, if we create scores over here, 77. If we create these scores, we should not we'll still see those come through, which is great.\u003C/p>\u003Cp>You know, we need to start working on our board. Alright. So let's flush this out a little bit more. Yeah. Maybe we want to grab the let's just work on our leaderboard first.\u003C/p>\u003Cp>That's what we'll do. We'll get some UI rolling for this. I'm just gonna replace this down here at the bottom. So we'll just drop this text out. Great.\u003C/p>\u003Cp>Just totally disappeared. And then we'll do let's add a add a heading for this. P high scores. Class font mono, font bold. Make this look really, really nice.\u003C/p>\u003Cp>Yeah. Text 2 XL, Text center. Add some styling to it. See what we get. High score should be white.\u003C/p>\u003Cp>We want all this to be white. Text white. Alright. There's our high scores. We'll go in and add some padding.\u003C/p>\u003Cp>Oh, I don't wanna pad that. Let's add padding to this. Maybe we do p 6, p 8, something like that. Alright. And then let's do a list of our high scores.\u003C/p>\u003Cp>So we'll do a list v 4 scores, 5 scores, and 7 scores ago. This will be grid. Grid calls 4, and then we'll just do, this is gonna be our score. Let's say 5. These could be a span, I guess.\u003C/p>\u003Cp>I don't know what the rules are about putting a p tag inside there. Span, we got John Ross, then we've got the message, and then we've got a time stamp. 3 minutes ago. Alright. Let's see what that looks like.\u003C/p>\u003Cp>Of course, it's not displaying. Let's put that text as lime green. Make it lime green. We'll make them bold as well. Do font mono.\u003C/p>\u003Cp>Okay. Alright. And then we probably got well, actually, let's make this our header row. Right? So we got the score.\u003C/p>\u003Cp>Got the user, And then we have the time. Alright. As far as our scores, we will make those extra large. Score, message, time. Great.\u003C/p>\u003Cp>Good. Okay. So as far as our scores, right, we're gonna need an array of scores. So let's go up, and we'll just add that. So we got scores here.\u003C/p>\u003Cp>Get your scores here. There's an array of those particular scores. And how do we, on a knit, we want to get those scores. Alright? So I could potentially fetch those via the REST client, but, let's use our put our heads together for this, and we'll do, we'll just send a message.\u003C/p>\u003Cp>Right? So let's create a quick function. We'll we'll reuse the same connection. So we're creating a subscription there, but let's create get initial scores, get initial scores. It's great.\u003C/p>\u003Cp>And then we're gonna do this where we say, direct us dot send message. So we're gonna send a message via WebSocket connection. We're gonna use the items type. We're going to use our read action, and then we have our collection of scores. We want to limit that to 10 scores.\u003C/p>\u003Cp>That makes sense. And then maybe we add oh, I'm sorry. This needs to be wrapped in a query though. Limit 10. And then maybe we want to sort those by value.\u003C/p>\u003Cp>So we're gonna get initial scores, and what? Okay. We'll just call get initial scores. Let's see what we've got when we open this up. So there's our data.\u003C/p>\u003Cp>Right? Looks good. But and those are sorted in the correct order. Alright. So let's add a handler for this.\u003C/p>\u003Cp>So maybe we add a UID for this as well so we can keep track of these. These are gonna be the initial scores. Alright. So if the event dot UID equals initial scores, we're going to populate the scores dot value with event dot data. Is it just dot data?\u003C/p>\u003Cp>I think it should just be this. Right? Alright. So we open up our view dev tools. We go to arcade cabinet.\u003C/p>\u003Cp>We'll just see. We've got some scores. Cool. Alright. So now that we are getting those scores, let's just iterate through those scores.\u003C/p>\u003Cp>So we'll do the same thing with the list. And this one we're gonna add a v 4. So that'll be a score and scores. Key will be the score dot ID, not scores, just score dot ID. And then we'll start populating.\u003C/p>\u003Cp>Score dot value. Why does it keep auto completing on me? Score dot username score dot message. And then the score dot timestamp. Alright.\u003C/p>\u003Cp>So we got our scores. Those are a little huge, so maybe we shrink those down. And maybe this is actually we want the score to be larger. Tex x l. Cool.\u003C/p>\u003Cp>And maybe we actually make these small. Okay. Alright. So I do have a function already, like a helper function in this. It's called get relative time.\u003C/p>\u003Cp>It is the function. It just returns like a a relative time stamp. There we go. 8 minutes ago. Looks great.\u003C/p>\u003Cp>Got our messages. We got our username. We got our scores. That's great. So now if we anytime we load this page, we're we're getting those initial scores.\u003C/p>\u003Cp>And, you know, now we've got to like, how are we gonna submit our other scores? How are we doing on time here? We got about 40 minutes left. So we we probably wanna build a form at the end of the game to track those scores and and allow people to submit them. But one of the other things that I wanna do here is maybe we use a a computed prop to to show these as well.\u003C/p>\u003Cp>We only want the latest ten scores maybe. Or, you know, we we could show the whole list, but then we're gonna have to resort those scores as well. So this is it might be a good thing that the computer prop is for. So we'll do, what, compute, const equals sorted scores equals computed. Oh, there we go.\u003C/p>\u003Cp>GitHub Copilot to the rescue, so we're at scores dot value. And then maybe that's what we use to show. Sorted scores. Alright. Do we still get the same thing?\u003C/p>\u003Cp>That's great. Okay. Awesome. And what else are we going to do? Maybe we add just a little bit of gap.\u003C/p>\u003Cp>Gap y 2. Oh, no. It does. It's not gonna be a gap. Let's do this where we do, like, a space y 2 below them.\u003C/p>\u003Cp>Do we need a divider there? Maybe we do have a divider. Class equals text at lime 500. I don't think that's having the effect that I want. Okay.\u003C/p>\u003Cp>Regardless, there we go. We've got this function. Now we want to, whenever we play the game, right, we need to submit a score. So at the end here, I've got 0 emails. Maybe we add another button for our actual scores.\u003C/p>\u003Cp>So we're gonna go into the game itself. Right. And what are we gonna do here? We're gonna add a couple things. Right.\u003C/p>\u003Cp>We probably want to, the Kaboom library is based on Canvas, so we're not going to have like HTML elements there to work with. So I'm thinking we just maybe show a modal. I've got this Nuxt UI library included in this starter kit for this, And I do know that they have a little modal component that we could pop up. So let's roll with that and see what we got. Alright.\u003C/p>\u003Cp>So inside the game, you can see here's all our variables for that. But let's just a knit a couple of of different variables up here at the top. We've got a, maybe like a show form. That'll be reactive. And then we have, like, a, what are we gonna call this?\u003C/p>\u003Cp>I don't wanna call it score because Kaboom uses that internally. Maybe we just call this the player score, and we could use just like a reactive object for this instead of a ref. And so we have a value for the score, we'll set that to 0 by default. We've got a message for that and we've got a username. So this is gonna be the data that we submit, when the game is completed.\u003C/p>\u003Cp>Alright. So scroll down. We'll probably need a submit function for that as well. So we'll do a function. And we don't have to use async here because we're gonna reuse that same WebSocket connection.\u003C/p>\u003Cp>Right? So we got submit score. Let's go up here to the top. We're gonna get our directus client. Directus equals use Nuxt app from that plug in.\u003C/p>\u003Cp>And inside here, let's do do we do like a try catch? Just in case there are errors. Cool. We are going to submit a message. Direct us, send message.\u003C/p>\u003Cp>And here, the type is gonna be what? It will be items again. So we wanna send to the items. We're gonna do an action. I don't need to put that there.\u003C/p>\u003Cp>Do action of create. The collection is gonna be scores, and then our data will be the player score. Okay? And then after that, we would not show the form, and then, yeah, maybe here we'll just console dot error any errors that we receive. Alright.\u003C/p>\u003Cp>So that's the scaffold here for this. We're gonna need a template for this. So we'll have to add that modal to it. So we'll scroll down. Scroll down.\u003C/p>\u003Cp>And then we we're gonna need to actually show that somewhere as well. So Canvas. Let's go back into the arcade cabinet. Class p t. Actually, gonna do do I need this?\u003C/p>\u003Cp>Yes. Let's place that here. P t minus what was it? P t 24 fix. Alright.\u003C/p>\u003Cp>So I'm gonna move that here. And can I just stick this model in there? U model v model equals show form. And do we want we don't want the overlay for this either. So let's do false on the overlay.\u003C/p>\u003Cp>And we'll say form here. Alright. Does that mess with anything on our game? I don't think so. If we go into our game, we should see this variable somewhere in show form.\u003C/p>\u003Cp>We can check the box, change that variable. Nope. Not updating it. But regardless, should be good. Okay.\u003C/p>\u003Cp>Why why can't you edit that? True. Just always set to false. Weird. Alright.\u003C/p>\u003Cp>Regardless, what we're we're gonna do now is, at the end of this game, we need to add a button to submit our scores. So we're gonna find the end scene. Is it end? No. What is this gonna be?\u003C/p>\u003Cp>Countdown. Let's just start countdown. Okay. So we're looking for the lose scene. And here inside the lose scene, we've got a restart button.\u003C/p>\u003Cp>We've got a text inside that button. So I'm just gonna copy these items here. Come down. This is gonna be the submit high score, score button logic just so we can comment. Let's call this the submit score button Submit.\u003C/p>\u003Cp>Score button. Is it gonna be the same color button? Maybe we make it purple. I don't know what the RGB is for purple. Purple RGB.\u003C/p>\u003Cp>Let's see. Like a blue violet. Okay. We'll roll with that. Not sure that's exactly what I want, but okay.\u003C/p>\u003Cp>And then on the submit score button dot on click, we want to show form. Nobody ever calls me. Show form equals true for the value and then inside the actual text for this, we're gonna say submit score. And that will be where we've got that restart button. We're gonna do submit score button dotpos.\u003C/p>\u003Cp>Okay. Alright. So if we did everything correct, hopefully, this will work. And we'll be able to see the submit score button. Okay.\u003C/p>\u003Cp>It's now taking the place of our other button, but we need to fix that. So let's just move it out of the way. Submit score button. Let's move it to the other side so we could see this is width divided by 4. Let's do the width.\u003C/p>\u003Cp>We'll just do the width of the canvas minus width divided by 4. Is that gonna get us what we want? We'll just quickly lose and and test to make sure. Lots of distractions on this episode as well. Alright.\u003C/p>\u003Cp>So submit. We can see a form here. That's looking great. Let's wrap this inside a card. Ucard.\u003C/p>\u003Cp>K. And then we're gonna build a form based on this. So, Nuxt has this UI library has some of these form elements. We're going to use a form group. We'll give it a label.\u003C/p>\u003Cp>What are we going to have for the label? We'll have the username. And inside that we'll have an input. So u input v model player score dot username. Let's see what that looks like.\u003C/p>\u003Cp>Okay. That looks nice. So also inside this, maybe we have a p, submit your score. Old text x l. Submit your score.\u003C/p>\u003Cp>We got a username. And then we want to actually show the score as well. Right? So fontmonotext. Violet.\u003C/p>\u003Cp>500. You deduct what? Player score dot value emails. Alright. So we're only showing that we ducked we ducked, just 0 emails.\u003C/p>\u003Cp>Right? So we're probably gonna have to fix that. Maybe we don't even need that actually. We'll just show you doc 0 emails. Add some padding between these 2.\u003C/p>\u003Cp>Just using space y 4, just a little helper class. Alright. Is that giving me what I want? No. It's not.\u003C/p>\u003Cp>Something about the card space y 4. Okay. And then we're gonna have another form group for our message. Alright. We got our message.\u003C/p>\u003Cp>You input that's actually gonna be you text area, v model. K. And what else do we need? We need some buttons. Submit, u button, cancel.\u003C/p>\u003Cp>So we get 2 buttons. Let's make this size large. And this will be let's wrap this in a div. Just flex them, give a little bit of gap between the 2. And this will be variant, what, like a outline, maybe?\u003C/p>\u003Cp>That's good. P class. K. And then we're gonna I don't know what happened to our spacing. Let's add that back.\u003C/p>\u003Cp>Okay. So there's our form. We've got submit score. You deduct 0 emails. The other thing that we're gonna do here, if we submit this score let's just test it out and see.\u003C/p>\u003Cp>Right? Bryant Ross. Submit. Are we actually getting the scores? We are not getting the score.\u003C/p>\u003Cp>Is that score showing up though? We refresh indirectus. Okay. So we just see that score show up, but we're not populating that score to the scores array, which we need to sort. And then, also, if we submit the scores again, you can see we've got the same message.\u003C/p>\u003Cp>So we probably want to reset the message every time. So let's just do this, player score dot, message equals null. Okay. So whenever we score, submit that score. Good.\u003C/p>\u003Cp>What's the other thing that we need to do? We need to iterate this player score reactive value anytime we increase the score. So here's a score plus plus. What we're gonna do here is player score dot value equals the score. So after we iterate, we're going to plug that score, and then we need to actually populate our scores here as well.\u003C/p>\u003Cp>Right? So if we go back to our arcade cabinet, we need another event handler here. So if the event dot UID equals scores dot sub, then we want to push that event data into the scores array, and that should trigger what we want. But, this is actually gonna be an array, so we can't really push that into the array. We need to destructure that.\u003C/p>\u003Cp>So, thinking this should get us where we want to be. Event data is not iterable. Let's scope this down. Event dot event equals create. And this is confusing.\u003C/p>\u003Cp>Right? Let's swap this out. Message. Change this over to message so that that is a little less confusing. Okay.\u003C/p>\u003Cp>So now we're seeing those scores populate correctly. Let's just give this a shot and see what we got. Alright. We're facing off against John Ross. Duct a couple of emails.\u003C/p>\u003Cp>We'll close this out. We'll submit our score. You ducked 4 emails. This is gonna be John Ross. Hi, guys.\u003C/p>\u003Cp>And we submit, and now we can see that score being populated automatically. So that's pretty freaking awesome. Right? What else do we want to do with this? Maybe we animate it a little bit?\u003C/p>\u003Cp>One of the libraries that I like to use that is actually included in this one is, v auto animate. Or, well, it's not, v auto. That's the directive for this. But it is, auto animate by the the fine folks at FormKit. So one line of code, you know, you've all seen this before.\u003C/p>\u003Cp>You add logic to it. But this one, it auto animates for you, giving you kind of a nice UI. So if we try this again, test message. Yeah. Probably have to refresh the page after I save it.\u003C/p>\u003Cp>But there we go. We've got our scores populating. These should be sorted in order as well. What do we have time left on the clock? We've got 22 minutes left.\u003C/p>\u003Cp>You know, we could go through and build a dashboard for this, but let's just kinda recap where we're at. We've got the real time tracking of the scores. We've got to submit a username. We are displaying those high scores. Let's add some sound effects.\u003C/p>\u003Cp>Let's just test it out one more time, make sure this is actually working first. Alright. Duck. Alright. Got 6, 7.\u003C/p>\u003Cp>Let's just die right now at this moment. And this will be Bry Ross is my username. You stink John. And I take my place on the leaderboard. Cool.\u003C/p>\u003Cp>Alright. So calling out when, you know, sound effects, maybe we could go in those. I'm not sure you're gonna be able to hear them though on this recording. So as far as a real time leaderboard, I I think we're calling that good. That is finished.\u003C/p>\u003Cp>Pretty savvy. Where could we take this from here? Right? We could go a a lot of different directions with this. You know, expanding this for, like, user authentication, maybe pull in, like, a GitHub username, something like that.\u003C/p>\u003Cp>But I'm pretty comfortable with this. I think I'm going to button this up and we'll probably actually ship this to the live version of this at directus. Is/arcade. I think this will look nice with a leaderboard on here. So that's it for this episode of 100 Apps, 100 Hours.\u003C/p>\u003Cp>I think this may be the shortest episode yet. That's it though. That's good. In and out. I hope to see you on the next one.\u003C/p>\u003Cp>Bye.\u003C/p>","Hi. Welcome back to another episode of 100 Apps, 100 Hours. I'm your host, Brian Gillespie, and today we are building something that I think is pretty cool. But here on 100 Apps 100 Hours, we build your suggestions, your favorite apps, or rebuild a clone of some other app in 1 hour or less, or publicly fail, get humiliated, trying. It was a lot of stress, to be honest with you. So the rules for this, number 1, we have 60 minutes to plan and build an application, no more, no less. And number 2, just use whatever you have at your disposal. So, could be AI, could be past projects, whatever we've got. We're just trying to speed run through applications. And today we've got a cool one. We're going to be building a real time leaderboard. So recently, as far as, like April Fools' Day, we put out this amazing game to make you a duckin' pro at cold email. So it's called duckin' cold emails. It's just a runner game where you face off against a sales rep who is sending you cold emails and pop ups on the screen here. So you duck or jump, you get a couple of lives, and at the end of it you can choose to play again, and you get a nice little score. So what we're going to do here is create a leaderboard for this, and riggity jig, away we go. Alright. So, 60 minutes to plan and build. Hopefully we don't need all of that, but maybe we will. That's how this works. Alright. So I usually like to start by mocking out the functionality that we need out of this. So what do I really want to happen here? When I play, I wanna be able to submit my score and if somebody else is playing around the world, I want that score to and they get a high score, I want that to pop up. I wanna see that. So we've got a real time tracking of high scores. This is really large. Let's just trim that down. Real time tracking of high scores, submit a username and message with that, maybe? Maybe we'll make this a little more competitive and that we can talk trash to each other while we do it. What else is there really? Display those scores. Display high scores. I'm gonna put maybe sound effects. I'm gonna put that in question mark. Maybe that'll be our stretch goal. So that's that's basically the functionality of this. Right? I don't know explicitly how long that's gonna take, but what do we have as far as a data model? That's one of the other things that I like to map out. We've got some actual players, but, you know, I'm not going to have a user that needs to log in for this. I'm just going to let we're going to do it NBA Jam style, where you can just key in your initials or something like that. So I'm just thinking here, there's probably only maybe just a single table for this, which is rare for us on a 100 apps to a 100 hours. We're going to have a value for the score, a username, there's gonna be an ID and a time stamp of of when that high score was, and then maybe a message. That seems like it's it to me. I don't wanna make this over complicated. Alright. So I'm gonna go into my direct assistance. This is totally blank. We're gonna create a new table. We're gonna call it scores. Can I zoom in on this? Yeah. There we go. We'll make it really fancy. We'll do the generated UUID. We've got date created. So this is a system field or optional system field that will basically whenever this item gets created it will pre populate some stuff for me. So basically what it does on create of this record in this collection, it will save the current date and time. So we'll unhide this because I do want to see that, And then we'll add a value. So as far as the number of emails ducked, I can't duck half an email. So that's gonna be an integer. Looks great. What else do we have? Maybe we'll make that half width. And a username. So go sweet handle. Cool. And then we're gonna have a message. So we'll just use the text area for that. The type is text, of course, so I could add, like, markdown or something for that if I wanted to, but we'll just keep this short and sweet. Alright. So as far as our data model, is that that's probably it. Right? Looks good. Getting on my toes here for this one. Alright. So we got this, specific application. It's basically just a Nuxt single page application. If I look, I've got a single index page, there's an arcade cabinet component that is doing most well, it's not really doing hardly any of the work it's just presentation. It gives us this nice arcade cabinet and some text down below. And then we also have oh, these were my buttons I was trying the last time I was in here. And then we have a game component. So the library that we're using for the actual game here is pretty interesting itself. You might go ahead and check it out if you are trying to build a game. Did we start the time? Oh, yeah. We did start the timer. Okay. It's called Kaboom JS. A pretty interesting library, actually. Makes it really easy to build quick little mini games like this, build stuff that's fun for your users. Alright. So that's the meat and potatoes of it. You know, there's some sprites, there's some functions, there's a couple of different scenes inside the game, but that's what we've got, right? So we've got our scores, let's go about setting up our real time connection. So Directus has real time baked into the platform. If I pull up my other project that I'm actually connected to, you can see I have enabled my WebSockets here. And away we go. Alright, so as far as the Knox application, I've just got a simple Directus plug in that provides me with a real time client. That's really all we've got. I've set the auth mode to public just so I've got, you know, anybody can connect to this. Again, we're not going to make authentication required here. What I'm also going to do is the ability to read scores and push scores. Okay. So what else do we have? I think that's pretty much it. Right? Let's dive in. Let's go to our arcade cabinet. Pull this up. Right. This is our wrapper component. I'm not sure where I'm gonna stick the leaderboard. Maybe up here at the top, maybe somewhere down here at the bottom. We could just cannibalize this bottom section down here. I'll zoom out in a little bit. But we're gonna get started with the SDK and WebSockets. So we've got a great guide on the documentation for this. It walks you through this in great detail. But we're just gonna pull this up. I'm gonna pull that Directus client. Direct cuts. Directus. I can't spell. Alright. We're gonna use the Nuxt app. Use Nuxt app composable to get that client. And then what we're gonna do is await directus dot connect. Alright. So that will connect and if we keep scrolling down, we can see that we've got a, open function here. You know, if we just wanna log that this was opened. We do this and we go back. And if I look in the console, should be getting a message that this was opened. Switching protocols. Not seeing any messages in here for whatever reason. Yeah. I don't see any particular messages, but let's keep moving on. We probably do need this directus dot message handler. Directus dot on web socket dot message console log events. What if we just log the entire event? Actually, that'll be a message. Same thing. Let's see what we get here. Do we have that connection established? Where is our connection? Why are we not seeing any messages through here? Right? Okay. Let's just add a subscription as well and see what we got. Oh, could it be our permission settings? Right? Let's log in and see. Directory's password. Alright. So if we look at no. We've got access to that. Alright. We've solved that problem already. Alright. So let's just create a subscription and see if we get that. Alright. So we'll go in and do directus dot subscribe, and we're gonna subscribe to the scores, And we're gonna get, we're gonna pass some options to this. So we want the event to be create. The query do we actually need a query? I don't think we let's not limit ourselves at this moment. And then maybe I wanna add, like, a UID to this, and we're gonna call this the scores sub. Subscription direct us dot subscribe. And let's see what we got here. Okay. So now I can see that particular message coming through. There's our subscription for this. So we're actually getting some data. Let's take a look and see if we open this up in a separate window. So we'll just open scores up. We'll create a new scores item, see if we get subscription for that. Yep. Okay. I see some data coming through. Great. So we are getting some data for our scores, which is good. Not sure why it's not showing the open function. Not really concerned with that, anyway. Alright. So what we wanna do, just one moment, please. We'll stop the clock. Okay. Had a hot call come in. We're restarting the clock. No extra work has been done, and I've totally lost track of where we were at in the process anyway. Alright. So, we've got our subscription. We can see that come through. You know, if we create scores over here, 77. If we create these scores, we should not we'll still see those come through, which is great. You know, we need to start working on our board. Alright. So let's flush this out a little bit more. Yeah. Maybe we want to grab the let's just work on our leaderboard first. That's what we'll do. We'll get some UI rolling for this. I'm just gonna replace this down here at the bottom. So we'll just drop this text out. Great. Just totally disappeared. And then we'll do let's add a add a heading for this. P high scores. Class font mono, font bold. Make this look really, really nice. Yeah. Text 2 XL, Text center. Add some styling to it. See what we get. High score should be white. We want all this to be white. Text white. Alright. There's our high scores. We'll go in and add some padding. Oh, I don't wanna pad that. Let's add padding to this. Maybe we do p 6, p 8, something like that. Alright. And then let's do a list of our high scores. So we'll do a list v 4 scores, 5 scores, and 7 scores ago. This will be grid. Grid calls 4, and then we'll just do, this is gonna be our score. Let's say 5. These could be a span, I guess. I don't know what the rules are about putting a p tag inside there. Span, we got John Ross, then we've got the message, and then we've got a time stamp. 3 minutes ago. Alright. Let's see what that looks like. Of course, it's not displaying. Let's put that text as lime green. Make it lime green. We'll make them bold as well. Do font mono. Okay. Alright. And then we probably got well, actually, let's make this our header row. Right? So we got the score. Got the user, And then we have the time. Alright. As far as our scores, we will make those extra large. Score, message, time. Great. Good. Okay. So as far as our scores, right, we're gonna need an array of scores. So let's go up, and we'll just add that. So we got scores here. Get your scores here. There's an array of those particular scores. And how do we, on a knit, we want to get those scores. Alright? So I could potentially fetch those via the REST client, but, let's use our put our heads together for this, and we'll do, we'll just send a message. Right? So let's create a quick function. We'll we'll reuse the same connection. So we're creating a subscription there, but let's create get initial scores, get initial scores. It's great. And then we're gonna do this where we say, direct us dot send message. So we're gonna send a message via WebSocket connection. We're gonna use the items type. We're going to use our read action, and then we have our collection of scores. We want to limit that to 10 scores. That makes sense. And then maybe we add oh, I'm sorry. This needs to be wrapped in a query though. Limit 10. And then maybe we want to sort those by value. So we're gonna get initial scores, and what? Okay. We'll just call get initial scores. Let's see what we've got when we open this up. So there's our data. Right? Looks good. But and those are sorted in the correct order. Alright. So let's add a handler for this. So maybe we add a UID for this as well so we can keep track of these. These are gonna be the initial scores. Alright. So if the event dot UID equals initial scores, we're going to populate the scores dot value with event dot data. Is it just dot data? I think it should just be this. Right? Alright. So we open up our view dev tools. We go to arcade cabinet. We'll just see. We've got some scores. Cool. Alright. So now that we are getting those scores, let's just iterate through those scores. So we'll do the same thing with the list. And this one we're gonna add a v 4. So that'll be a score and scores. Key will be the score dot ID, not scores, just score dot ID. And then we'll start populating. Score dot value. Why does it keep auto completing on me? Score dot username score dot message. And then the score dot timestamp. Alright. So we got our scores. Those are a little huge, so maybe we shrink those down. And maybe this is actually we want the score to be larger. Tex x l. Cool. And maybe we actually make these small. Okay. Alright. So I do have a function already, like a helper function in this. It's called get relative time. It is the function. It just returns like a a relative time stamp. There we go. 8 minutes ago. Looks great. Got our messages. We got our username. We got our scores. That's great. So now if we anytime we load this page, we're we're getting those initial scores. And, you know, now we've got to like, how are we gonna submit our other scores? How are we doing on time here? We got about 40 minutes left. So we we probably wanna build a form at the end of the game to track those scores and and allow people to submit them. But one of the other things that I wanna do here is maybe we use a a computed prop to to show these as well. We only want the latest ten scores maybe. Or, you know, we we could show the whole list, but then we're gonna have to resort those scores as well. So this is it might be a good thing that the computer prop is for. So we'll do, what, compute, const equals sorted scores equals computed. Oh, there we go. GitHub Copilot to the rescue, so we're at scores dot value. And then maybe that's what we use to show. Sorted scores. Alright. Do we still get the same thing? That's great. Okay. Awesome. And what else are we going to do? Maybe we add just a little bit of gap. Gap y 2. Oh, no. It does. It's not gonna be a gap. Let's do this where we do, like, a space y 2 below them. Do we need a divider there? Maybe we do have a divider. Class equals text at lime 500. I don't think that's having the effect that I want. Okay. Regardless, there we go. We've got this function. Now we want to, whenever we play the game, right, we need to submit a score. So at the end here, I've got 0 emails. Maybe we add another button for our actual scores. So we're gonna go into the game itself. Right. And what are we gonna do here? We're gonna add a couple things. Right. We probably want to, the Kaboom library is based on Canvas, so we're not going to have like HTML elements there to work with. So I'm thinking we just maybe show a modal. I've got this Nuxt UI library included in this starter kit for this, And I do know that they have a little modal component that we could pop up. So let's roll with that and see what we got. Alright. So inside the game, you can see here's all our variables for that. But let's just a knit a couple of of different variables up here at the top. We've got a, maybe like a show form. That'll be reactive. And then we have, like, a, what are we gonna call this? I don't wanna call it score because Kaboom uses that internally. Maybe we just call this the player score, and we could use just like a reactive object for this instead of a ref. And so we have a value for the score, we'll set that to 0 by default. We've got a message for that and we've got a username. So this is gonna be the data that we submit, when the game is completed. Alright. So scroll down. We'll probably need a submit function for that as well. So we'll do a function. And we don't have to use async here because we're gonna reuse that same WebSocket connection. Right? So we got submit score. Let's go up here to the top. We're gonna get our directus client. Directus equals use Nuxt app from that plug in. And inside here, let's do do we do like a try catch? Just in case there are errors. Cool. We are going to submit a message. Direct us, send message. And here, the type is gonna be what? It will be items again. So we wanna send to the items. We're gonna do an action. I don't need to put that there. Do action of create. The collection is gonna be scores, and then our data will be the player score. Okay? And then after that, we would not show the form, and then, yeah, maybe here we'll just console dot error any errors that we receive. Alright. So that's the scaffold here for this. We're gonna need a template for this. So we'll have to add that modal to it. So we'll scroll down. Scroll down. And then we we're gonna need to actually show that somewhere as well. So Canvas. Let's go back into the arcade cabinet. Class p t. Actually, gonna do do I need this? Yes. Let's place that here. P t minus what was it? P t 24 fix. Alright. So I'm gonna move that here. And can I just stick this model in there? U model v model equals show form. And do we want we don't want the overlay for this either. So let's do false on the overlay. And we'll say form here. Alright. Does that mess with anything on our game? I don't think so. If we go into our game, we should see this variable somewhere in show form. We can check the box, change that variable. Nope. Not updating it. But regardless, should be good. Okay. Why why can't you edit that? True. Just always set to false. Weird. Alright. Regardless, what we're we're gonna do now is, at the end of this game, we need to add a button to submit our scores. So we're gonna find the end scene. Is it end? No. What is this gonna be? Countdown. Let's just start countdown. Okay. So we're looking for the lose scene. And here inside the lose scene, we've got a restart button. We've got a text inside that button. So I'm just gonna copy these items here. Come down. This is gonna be the submit high score, score button logic just so we can comment. Let's call this the submit score button Submit. Score button. Is it gonna be the same color button? Maybe we make it purple. I don't know what the RGB is for purple. Purple RGB. Let's see. Like a blue violet. Okay. We'll roll with that. Not sure that's exactly what I want, but okay. And then on the submit score button dot on click, we want to show form. Nobody ever calls me. Show form equals true for the value and then inside the actual text for this, we're gonna say submit score. And that will be where we've got that restart button. We're gonna do submit score button dotpos. Okay. Alright. So if we did everything correct, hopefully, this will work. And we'll be able to see the submit score button. Okay. It's now taking the place of our other button, but we need to fix that. So let's just move it out of the way. Submit score button. Let's move it to the other side so we could see this is width divided by 4. Let's do the width. We'll just do the width of the canvas minus width divided by 4. Is that gonna get us what we want? We'll just quickly lose and and test to make sure. Lots of distractions on this episode as well. Alright. So submit. We can see a form here. That's looking great. Let's wrap this inside a card. Ucard. K. And then we're gonna build a form based on this. So, Nuxt has this UI library has some of these form elements. We're going to use a form group. We'll give it a label. What are we going to have for the label? We'll have the username. And inside that we'll have an input. So u input v model player score dot username. Let's see what that looks like. Okay. That looks nice. So also inside this, maybe we have a p, submit your score. Old text x l. Submit your score. We got a username. And then we want to actually show the score as well. Right? So fontmonotext. Violet. 500. You deduct what? Player score dot value emails. Alright. So we're only showing that we ducked we ducked, just 0 emails. Right? So we're probably gonna have to fix that. Maybe we don't even need that actually. We'll just show you doc 0 emails. Add some padding between these 2. Just using space y 4, just a little helper class. Alright. Is that giving me what I want? No. It's not. Something about the card space y 4. Okay. And then we're gonna have another form group for our message. Alright. We got our message. You input that's actually gonna be you text area, v model. K. And what else do we need? We need some buttons. Submit, u button, cancel. So we get 2 buttons. Let's make this size large. And this will be let's wrap this in a div. Just flex them, give a little bit of gap between the 2. And this will be variant, what, like a outline, maybe? That's good. P class. K. And then we're gonna I don't know what happened to our spacing. Let's add that back. Okay. So there's our form. We've got submit score. You deduct 0 emails. The other thing that we're gonna do here, if we submit this score let's just test it out and see. Right? Bryant Ross. Submit. Are we actually getting the scores? We are not getting the score. Is that score showing up though? We refresh indirectus. Okay. So we just see that score show up, but we're not populating that score to the scores array, which we need to sort. And then, also, if we submit the scores again, you can see we've got the same message. So we probably want to reset the message every time. So let's just do this, player score dot, message equals null. Okay. So whenever we score, submit that score. Good. What's the other thing that we need to do? We need to iterate this player score reactive value anytime we increase the score. So here's a score plus plus. What we're gonna do here is player score dot value equals the score. So after we iterate, we're going to plug that score, and then we need to actually populate our scores here as well. Right? So if we go back to our arcade cabinet, we need another event handler here. So if the event dot UID equals scores dot sub, then we want to push that event data into the scores array, and that should trigger what we want. But, this is actually gonna be an array, so we can't really push that into the array. We need to destructure that. So, thinking this should get us where we want to be. Event data is not iterable. Let's scope this down. Event dot event equals create. And this is confusing. Right? Let's swap this out. Message. Change this over to message so that that is a little less confusing. Okay. So now we're seeing those scores populate correctly. Let's just give this a shot and see what we got. Alright. We're facing off against John Ross. Duct a couple of emails. We'll close this out. We'll submit our score. You ducked 4 emails. This is gonna be John Ross. Hi, guys. And we submit, and now we can see that score being populated automatically. So that's pretty freaking awesome. Right? What else do we want to do with this? Maybe we animate it a little bit? One of the libraries that I like to use that is actually included in this one is, v auto animate. Or, well, it's not, v auto. That's the directive for this. But it is, auto animate by the the fine folks at FormKit. So one line of code, you know, you've all seen this before. You add logic to it. But this one, it auto animates for you, giving you kind of a nice UI. So if we try this again, test message. Yeah. Probably have to refresh the page after I save it. But there we go. We've got our scores populating. These should be sorted in order as well. What do we have time left on the clock? We've got 22 minutes left. You know, we could go through and build a dashboard for this, but let's just kinda recap where we're at. We've got the real time tracking of the scores. We've got to submit a username. We are displaying those high scores. Let's add some sound effects. Let's just test it out one more time, make sure this is actually working first. Alright. Duck. Alright. Got 6, 7. Let's just die right now at this moment. And this will be Bry Ross is my username. You stink John. And I take my place on the leaderboard. Cool. Alright. So calling out when, you know, sound effects, maybe we could go in those. I'm not sure you're gonna be able to hear them though on this recording. So as far as a real time leaderboard, I I think we're calling that good. That is finished. Pretty savvy. Where could we take this from here? Right? We could go a a lot of different directions with this. You know, expanding this for, like, user authentication, maybe pull in, like, a GitHub username, something like that. But I'm pretty comfortable with this. I think I'm going to button this up and we'll probably actually ship this to the live version of this at directus. Is/arcade. I think this will look nice with a leaderboard on here. So that's it for this episode of 100 Apps, 100 Hours. I think this may be the shortest episode yet. That's it though. That's good. In and out. I hope to see you on the next one. Bye.","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,122,158,159,160,161,162,163],"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","d072a935-906e-4208-a5dc-e9b117d0ab29","b9f1d4cf-f53c-49db-9e87-adf7e3b9ff99","6bff0c09-ad87-4d5c-b227-89b8c3c02220","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},"Build a live realtime leaderboard for an online arcade game",{"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",1773850438462]