[{"data":1,"prerenderedAt":437},["ShallowReactive",2],{"footer-primary":3,"footer-secondary":93,"footer-description":119,"100-apps-100-hours-form-builder":121,"100-apps-100-hours-form-builder-next":168,"sales-reps":185},{"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},"383c24d5-b6b5-4d66-aba6-6997af5f77b4","form-builder","988084197","Join Bryant for his final 60 minute rush of the season as he attempts to plan and build a form builder. ","23e92277-c632-4fcb-8253-71f79d984430",62,10,"2024-07-25","Mission: Form Builder","\u003Cp>Speaker 0: Welcome back to another episode of 100 apps, 100 hours where we try to build or rebuild some of your favorite apps and app ideas in 60 minutes or less or die trying. Emphasis on the die trying. But, if you've not seen the show before, there are basically two rules. You have 60 minutes to plan and build, no more, no less. And the second rule, the anti rule, use whatever you have at your disposal.\u003C/p>\n\u003Cp>So today, let's get into the episode, we are going to be building a form builder slash Typeform clone. If you're watching this, you're probably familiar with Typeform, but let's just run through it really quickly. Basically, it is a form builder that, does questions. It's like 1 question at a time. You can customize the design a bit.\u003C/p>\n\u003Cp>It kinda looks like this where, hey, we fill out a form. I have to do an actual email here. Then, another question shows up. We go through this. Another question shows up.\u003C/p>\n\u003Cp>There's a lot of configurability there, but that's it really. It's a nice looking form builder that that hopefully makes people wanna fill out your forms. That's it. That's what we're gonna build. So let's dive into it.\u003C/p>\n\u003Cp>We will put where's my where's my widget? Where's my widget? Timer. Where's the time? Oh, they moved the timer on me.\u003C/p>\n\u003Cp>Okay. So we're gonna put 60 minutes on the clock. Let's roll with this. Alright. So let's start kind of planning this out.\u003C/p>\n\u003Cp>Right? I always like to start with the functionality. Feature what features do we need? Right? We need to be able to build a form, fields, field types, etcetera.\u003C/p>\n\u003Cp>How do I there we go. And then we want to be able to submit that form. Maybe render and submit form. Boom. That's it.\u003C/p>\n\u003Cp>Really? That's all the features that we need for this. We wanna build a form, maybe we call this a schema. Then we wanna render the form and submit the form on a front end. Alright.\u003C/p>\n\u003Cp>So how do we go about actually doing this? Now, there's a project that I created previously called hgos that has a kind of form builder in there. It is, not the most robust solution. So, the setup there is basically using the JSON repeater inside Directus, which is basically, just an array of form fields that we're gonna use. We pass that JSON to our component on the front end that renders a form dynamically based on the different types of inputs that we want.\u003C/p>\n\u003Cp>And then when you submit that, it all goes into a single table, into a single collection. So that is super flexible. And and if all you need to do is, output a a JSON schema to create a form and have a single collection, where you store all that data as JSON, great. Let's take this a step further in this episode though. And let's add this, make this a little more robust.\u003C/p>\n\u003Cp>So when I think about the data model, we're going to have a form or forms. Each one of those forms will probably have some form fields. Great. And then on the submission side, we've got like a form submissions. And within that, we have an ans we have answers, I guess.\u003C/p>\n\u003Cp>And so we've got the form submissions belong to the forms, the answers belong to the form submissions. Just kind of drawing these things out. There'll be a relationship between the answers and the form fields. I skipped the magic arrows here. Great.\u003C/p>\n\u003Cp>There we go. So this is kinda the way I've got it in my mind. We've got a form that holds all of the form fields. There's probably a title. We've got like a redirect or or some kind of on submit action, what we wanna do with it.\u003C/p>\n\u003Cp>On the front end, we fetch that form and all the form fields, we render it. When somebody fills out that form, we create a form submission, and that form submission is made up of answers and probably some metadata about when that form was submitted, who's submitting it, etcetera. That sounds like a good start for our planning. I'm just gonna throw these up side by side so I don't forget. And to start this out, I've got my blank instance of Directus, that's what we're using on the back end here.\u003C/p>\n\u003Cp>We're just going to hit create a new collection. Let's call this Forms. Now I'm gonna use the UUID for this. Maybe we just do the standard format here. User created, date created, date updated.\u003C/p>\n\u003Cp>I'm gonna wanna know what the status of these forms is. Do we need a sort for the forms? Maybe. Do we need a status? We'll just go ahead and add it.\u003C/p>\n\u003Cp>Alright, so let's give the form a title, maybe a form description, so that'll be a text area. And then we're going to create another collection called Form Fields. So we'll just go in, create a new collection. What's happening behind the scenes, obviously Directus is creating these tables in MySQL database. The fields, we definitely need to sort on the fields.\u003C/p>\n\u003Cp>Do we want a status on those? Maybe, maybe not. No. Let's keep status off of this. You know, do we want to know when the actual form fields were updated?\u003C/p>\n\u003Cp>Maybe I'm not super concerned with that, so we'll just leave those off. All right. So for the form fields, when we're thinking through this, right, what are we gonna need on our actual form fields? We're gonna need a name for the field. So what's the field name?\u003C/p>\n\u003Cp>We're gonna need a type for the field. So the type is probably gonna be a string, but maybe as far as, like, the interface that I wanna use, I want this to be a drop down. So we can think about input. Great. Do we have a, like, a form field for that or an icon?\u003C/p>\n\u003Cp>Yeah. Let's try that. Let's do text area. Text area. Text area.\u003C/p>\n\u003Cp>Do we have one for that? We don't. We'll just go with that option. What else do we wanna display? Like a select?\u003C/p>\n\u003Cp>Just thinking along the lines of the actual HTML form elements we're gonna use here. Drop down, maybe select. Good looking icons are sparse here. Okay. So that's probably good for now.\u003C/p>\n\u003Cp>We'll just make this a label, format each label, we don't want to show as a dot. I can actually copy and paste the choices for our interface into the display here. And this is what shows up on the, like, the index pages where you could potentially see a list of the form fields. Alright, so we've got a name, let's make this half width, make it look good when we're actually creating our form. We've got a name, I could double as label.\u003C/p>\n\u003Cp>Trying to think of what else we've got on this. Maybe some type of validation name and type. If it is a select type, we probably need, like, some available choices. Right? So let's go ahead and do that.\u003C/p>\n\u003Cp>We'll do that as the, like, JSON repeater. We'll call this select options, I guess. Great. We'll add a text field. So this is the text we're gonna display or we use kind of the label value format.\u003C/p>\n\u003Cp>So we set that up, that'll be an input, very simple. And then we have a value. In case we wanna store those differently, we can require a value, make that half width as well. Great. And let's add some conditionality to this that, if the type is select that's when we show this.\u003C/p>\n\u003Cp>So I'm gonna go into our field options, let's just hide this by default, and then inside Directus I can add conditionals to the forms inside the Studio by doing something like this. So we'll say show if type equals select. And this is just a description so I can keep my logic together. We'll say if the type equals select we're going to make sure this is not hidden, and that should give me what I am looking for. Right?\u003C/p>\n\u003Cp>We could expand this further and add, like, conditions to our front end and etcetera. What we need to do now, though, is add the relationship between those. So in this case, we want a form field. It should only belong to a single form in my mind. I I you could I guess you could make the argument either way, but in this case, let's just create that relationship.\u003C/p>\n\u003Cp>So this is gonna be a one to many relationship here. So this is gonna be called Fields, and I'm inside our forms collection, our forms table right now. I'm just going to call this key fields and we're going to look, the related collection is our form fields. And the foreign key inside the form fields table, I'm going to create a new field called form that relates back to the actual form. Gravy will show a link to the item.\u003C/p>\n\u003Cp>And if I open up this advanced field creation mode inside Directus, I get a lot more options that I can configure and play with. So I'm going to add a sort field, we're just going to make sure that sort is selected here. That allows us to drag and drop the order of those fields. And then for our triggers, like if we deselect a form field, do I want to delete that item or not? In this case I'm just gonna nullify the form field.\u003C/p>\n\u003Cp>Great, we'll display the related values. Bada bing bada boom. Now we should have a form with some fields. Let's just test this out really quickly. So we're gonna go in, we got a form.\u003C/p>\n\u003Cp>This is our contact us form, the most standard of all forms on the Internet. And here's the standard contact us form. Great. Alright, so we'll do name, is gonna be email, this will be an input. Great.\u003C/p>\n\u003Cp>What are some of the other things that we'd probably want to add to this? Like a placeholder, I would say. First name, that'll be an input. What else? Last name.\u003C/p>\n\u003Cp>Okay. Comments, that'll be maybe a text area. And now let's, let's get into the select option. Right? How can we help you?\u003C/p>\n\u003Cp>Alright. So for the options here, I need a developer, so we'll give it a label. Maybe we store the value as just developer. I need a sales rep. I don't know.\u003C/p>\n\u003Cp>Great. Okay. So a couple of things here. I don't see the actual form values that I I might look. You know, here's they're just showing IDs.\u003C/p>\n\u003Cp>How can I fix that, right? I want to make sure this looks nice when we're setting these things up. So what I can do, if I go into my form fields, we have the display template. So this is the default anytime we're referencing this form field. Or I could go into forms and go to the fields section here and I can control the display template just within forms.\u003C/p>\n\u003Cp>So that's super handy if you, need different display templates in different places, but in this case let's just go and edit the default one. Let's do the name and then we'll maybe put like a special little dot there and maybe we want to show the type of field that is. And now if we just go back to our forms, great, we can kind of see what we're working with here. I don't really let's put first name, last name, then email, then comments, and how can we help you? Maybe we move that up above.\u003C/p>\n\u003Cp>Right. Great. Alright. So now we've got the start to this form. Let's go in and actually start messing with the front end.\u003C/p>\n\u003Cp>We've got 47 minutes on the clock. I'm just gonna pull this up. Let's pull up my Nuxt application. This is just the standard Nuxt application that I have out of the box. That's my weapon of choice in this case.\u003C/p>\n\u003Cp>And let's just go in, I'm going to create a new directory in the pages directory. So this will give us a route automatically on the front end. I'm gonna call this forms, and then I'm gonna do just form in brackets here, and that will give us, like, a a dynamic form. If I do view script setup, TS setup, this is kind of what I want. Great.\u003C/p>\n\u003Cp>We've got the standard Vue Form Component. And we'll just say Form Component. Great. Alright. So let's test this out on the frontend, forms.\u003C/p>\n\u003Cp>Test. What? Uh-oh. Some type of issue going on. There it is.\u003C/p>\n\u003Cp>Form component. And this has got a layout attached to it where we're showing, like, the logo and things like that. Maybe not a huge issue for this, but, you know, the type form has, like, this full screen form effect, so, you know, maybe we dive into that later. Alright. So now I actually want to fetch that form from Directus, right?\u003C/p>\n\u003Cp>So a couple things, we probably need to edit our access control. So by default, Directus locks down every single thing inside your instance. Only admins are able to, interact with the API by default. So we're just going to go in. I'm gonna set readability for our forms and our form fields, and now if I just wanted to test that out quickly, I could do something like this in the browser where I go to my directus url/items/forms, and I can see I get the form data here, and and we'll expand that, once we dive into the Nuxt side of things.\u003C/p>\n\u003Cp>Right? So the first thing that we want to do here, we're gonna get the routes, so we know which form to fetch. And then we're gonna be using the Nuxt Async Data Call. So this Nuxt application, I've got a Directus plug in here that basically fetches information from the Directus API. We provide the SDK to the Nuxt application so I could do things like this where I just simply say give me the Directus client.\u003C/p>\n\u003Cp>And that way it's shared across the actual Nuxt application. So I get my Directus client, and then we're gonna use the, let's say data is coming back. We're gonna use the Async data call from Nuxt. And within that we are going to return Directus dot request. And then we need to import the readitem from the Directus SDK.\u003C/p>\n\u003Cp>Alright. So this is just going to read a single item. We're gonna pick that up from the actual form, from the route itself. So we'll say directives dot request. It'll be read item.\u003C/p>\n\u003Cp>We're reading items from the form collection. And then we've got route dot params dot form. Right? So that will it should give us the form. And then I can pass a a set of parameters to the Directus API to get things like form fields, etcetera.\u003C/p>\n\u003Cp>But let's just take it from here. We'll wrap this in another div. I always end up doing this just to take a look at my data and just wrap it in a pre tag. And now if I do forms test, we're probably not gonna get back any data. If I was to open this up, I don't really see anything here.\u003C/p>\n\u003Cp>Great. Alright. So now let's actually test this with the ID of one of the forms. We'll go in. Great.\u003C/p>\n\u003Cp>Okay. Looks like we are getting a cores error. So I've goos something up in my direct to settings. CORS enabled. CORS origin.\u003C/p>\n\u003Cp>Should be interesting here. Let me restart this Docker container and see if that solves my problem. If I look at my ENV, I've got HTTP local host 8055. That is that's where my direct instance is running. Okay.\u003C/p>\n\u003Cp>There we go. I just needed to set the cores in this case to make sure that local host 3,000 is allowed, and basically, I've done that the quick and dirty way just by allowing any origin here. But, typically, I would set this to, like, local host or something like that. Alright. So we could see here's our form data.\u003C/p>\n\u003Cp>That's great. How do we actually get the form fields? One of the nice things about the Direct Express API is the ability to call this data in a single API call to expand those relationships, right? So inside my form fields, maybe on the front end, I don't really care about this extra data when it was created. So let's just say we want the ID, we want the name of the form, we want the oh, it's actually the title of the form.\u003C/p>\n\u003Cp>Title. Great. And we want the description. And then we get into the fields. All right, so I could do something like this where I add a dot notation and a wildcard which will give me all the items for that field or all the all the fields within the fields collection or the the related items there basically.\u003C/p>\n\u003Cp>So instead, what I'm gonna do, if you're using the SDK, we could pass it a syntax like this where we say fields. And here, we're gonna specify the actual fields. We want the name. We want the type. We want the options, and that should give us what we're looking for here.\u003C/p>\n\u003Cp>Right? And we probably do need the ID of those fields as well. We'll look at submissions in a few. Okay, so now we've got these fields coming in, this looks good, we've got a type, we've got a name for these, we can start to construct like a form with this actual data. Now on the front end in this Nuxt application, I have this Nuxt UI library.\u003C/p>\n\u003Cp>It would be suicide to try to build this without a form library, ahead of time here or, like, some type of UI library so I could do forms. They've got this really nice setup where you can pass it a form component. They've got Zod validation, support for Yelp and Joy if you're into that. But looking at this, everything gets wrapped in this form group component, which gives you like a nice label, etcetera. So let's just try to iterate through this.\u003C/p>\n\u003Cp>Right? We've got a form. They have a u form component. Not concerned with, like, validation at this moment. Let's just display a form.\u003C/p>\n\u003Cp>Right? So we're gonna go through Uform group, v 4 fields and forms dot fields. The field, we wanna pass like a I think there's a name component for each one of these. Like, if we go in and look at the props, we gotta pass a name to it. So we'll do name.\u003C/p>\n\u003Cp>It's field dot name. Great. What else do we need here? So within the form group, then we are going to do the actual component. So we've got an input here.\u003C/p>\n\u003Cp>There's a select box. Great. What else do we have? We had text area. Right?\u003C/p>\n\u003Cp>So what I'm going to do, I'm just gonna create a map here, basically. So we'll do, a field map. And I'm just gonna do something like this where, that actually looks pretty good. You input, you text area. That's how all these are name spaced.\u003C/p>\n\u003Cp>But what I wanna do instead is use this, like, resolve component or, you know, I could even actually import these as well. So this resolve component is just a helper. You could just straight import them as well. And with Nuxt, you can actually load these dynamically as well by adding lazy in the front of them. So it only pulls in that, that component when it actually needs it dynamically.\u003C/p>\n\u003Cp>Alright. So we've got our fields, we've got our form groups within the form, and GitHub Copilot is helpful again in this aspect that we want to do the field dot type inside. We wanna pull that correct component from our map, give it a name, give it an options. Let's take a look. Options are there.\u003C/p>\n\u003Cp>Name and value. Okay. So let's save this, see what we get. It looks like we have some we got some form of something going on. I need to change this right away to input so we can actually see those.\u003C/p>\n\u003Cp>Let's give this a little bit of styling. Right? Maybe we do, like, a max width XL. Format with math width Excel MX Auto. Great.\u003C/p>\n\u003Cp>Maybe we make this a grid. We give it some gap. Why is this actually not should be parsing out my is Tailwind not included in this? What what have we got going on? My styling is not being applied from Tailwind.\u003C/p>\n\u003Cp>That's kind of upsetting. We can definitely sort that out though. And then we need a label for this as well. So we'll do the field dot name for the label. Okay, so now we've got a kinda set up in there.\u003C/p>\n\u003Cp>Let's do view button. And we'll do, like, a submit option here. Let's add that outside the form group. So we get a submit button at the bottom of this. Right?\u003C/p>\n\u003Cp>Okay. So how can we make this kind of like typeform in a way because we wanna answer these one question at a time? Or how can we kind of loop through these? Right? Gillespie.\u003C/p>\n\u003Cp>Great. We don't have a state for these, so we'll we'd need to update that as well. Let's do v model equals set up, like, our form data. Form data, maybe we use like a ref for this. It'll be form data, and we can dynamically do field dot ID.\u003C/p>\n\u003Cp>Let's put this in like an answers array. Answers. And then we're gonna have the field dot ID. Okay. Alright.\u003C/p>\n\u003Cp>And within this, I'm just gonna do answers, and we'll just preset that. All right. So now if I take a look at like my Vue Dev Tools and we go to Form, Okay. I can see my Directus object. I've got my form data.\u003C/p>\n\u003Cp>There's my answers. Now if I populate these, is that actually doing what I want? More data. Click form. Do we see our answers?\u003C/p>\n\u003Cp>Oh, maybe we want to grab the index of this as well. Field index inform dot fields. That could be helpful. Answers field index field dot id. Bryant Gillespie.\u003C/p>\n\u003Cp>Bryantdirectus.io. Let's take a look at the data we're pushing here. Okay. So I can see my fields now. Oh, nope.\u003C/p>\n\u003Cp>Form data. There's my answers. Okay. That'll that'll suffice for now. All right.\u003C/p>\n\u003Cp>So let's do these one at a time as well, right? So maybe we give this a const current field idx. We set that to 0. And then, how are we gonna want to display this? Alright.\u003C/p>\n\u003Cp>So we're gonna render each field conditionally. Alright. So let's just wrap this in a template. This will be the what are we gonna do here? So we still need to show the actual field.\u003C/p>\n\u003Cp>V. This could actually be something like this, I think. V if current field current field IDX equals field IDX template. We'll loop for these Fields. Field IDX, inform dot fields key equals field dot field.\u003C/p>\n\u003Cp>Nope. Field.id. Alright. Let's see what we've got here. Oh, don't wanna close.\u003C/p>\n\u003Cp>Let me open this up. Alright. V field dot ID. We don't need a key on that one. V4fieldsfield.\u003C/p>\n\u003Cp>Alright. So now we've got kind of this one field for each. One of the cool utilities that I use a lot is also the auto animate by a group called FormKit. It's, v autoformkit auto animate. One line of code or just a couple lines of code and you've got, like, nice form animations, for, like, list transitions, showing and hiding inputs, etcetera.\u003C/p>\n\u003Cp>And we're only gonna show the button if fields, field idx is equal to form dot fields dot length. Field.ex +1 is equal to form.fields.length. So here we shouldn't be showing that button. Give us a padding here. Cool.\u003C/p>\n\u003Cp>Alright. Now within this, we probably need, like, a previous and next button as well. So let's just do, like, a flexbox, Go to some gap. We'll do U button. Next.\u003C/p>\n\u003Cp>Okay. GitHub Copilot. Alright. So basically, if the current field IDX is greater than 0, we're going to display the previous button. If the current field IDX is not greater than the end of the form, we want to show that.\u003C/p>\n\u003Cp>But we need to fix the actual setup here. You form group. Maybe we wrap this in a div. Okay. So now you could see we could kinda go through each one of these.\u003C/p>\n\u003Cp>And do I have the actual auto enemy applied properly in this case? Via, it should be. It's not actually animating. Let me refresh. Ah, there we go.\u003C/p>\n\u003Cp>Now we're getting some nice animation or animations. Right? So we'll do some margin there. Add justification between these. And we do one of these as variant equals soft.\u003C/p>\n\u003Cp>Is that what it is? There we go. Alright, so this is the primary. Great. There we go.\u003C/p>\n\u003Cp>Cool. Alright. So now I could go through and fill out this form. Alright. Oh, that's actually submitting the form.\u003C/p>\n\u003Cp>I don't wanna do that. Alright. Next. Gillespie. May we change this to button?\u003C/p>\n\u003Cp>Great. Give him my email. Cool. Starting to look something like Typeform in this case. Alright.\u003C/p>\n\u003Cp>So test, test, test. V if field equals fields dot length. Oh, maybe this is the actual code that we need here. Why are we not seeing the so, oh, because it's not in that actual loop. There we go.\u003C/p>\n\u003Cp>So now we should see the submit button. Great. We don't have any type of handler on the submit button. And it's also not doing anything if we actually, were to submit this anyway because we've got, we we don't have anywhere to post it inside Directus. Right?\u003C/p>\n\u003Cp>We've got roughly 20 some odd minutes. Let's make this form actually submit. Right? So if we go back to our setup, right, we've got, a form submission, we've got some answers that are within that. So let's go ahead and set up those collections.\u003C/p>\n\u003Cp>Let's say form submissions. These will be DUID. This will be let's call this submitted, date submitted. That kinda matches what we've got already. Yeah.\u003C/p>\n\u003Cp>Maybe we wanna track the time that it actually user created, date updated, user updated, the status of this submission. You know, you could even like, building your own here, you could certainly, like, track partial submissions and things like that as well if you wanted to. Like, we could submit any time and then update whenever they submit the final one, sort of thing. But let's say we have, like, a date started type of thing. Maybe we wanna, like, store that whenever they start this form submission.\u003C/p>\n\u003Cp>So we could track things like how long it actually took them to do this. For that we want the date time. We're just gonna do time stamp. Cool. Day started.\u003C/p>\n\u003Cp>Date submitted. Let's make sure we show that field. I'm just going to make one of these full width so I can get what I want out of this. Okay. Alright, so the form submission, we've got to link that back to an actual form as well.\u003C/p>\n\u003Cp>So we will do a mini to 1 here. This will be our forms. And let's do forms as the related collection. Now if I open up advanced mode, I can also create the inverse relationship as well. So if I wanted to do something like this where inside forms I've got a relationship back to the submissions, which in this case makes a lot of sense because of probably want to see the submissions.\u003C/p>\n\u003Cp>We could set up that and create that relationship in a single go. So there's our form. And then the next thing that we want to do is create a form, I'm gonna call it form submission answers. It could just be form answers, I guess. Always like to, have like a a common prefix, among these things, especially when you start building projects that have a ton of tables in them.\u003C/p>\n\u003Cp>Alright. So there's probably a sort on that specific answer. Do we need any of these additional fields? Probably not, right? So when I think of the answer, we've got a relationship back to the form field.\u003C/p>\n\u003Cp>So we'll just call this field. Let's Create that. There's the form fields collection. And again, I could do the same kind of thing. And setting this up this way, you know, with all the different database tables and the collections inside Directus allows us to, you know, say I wanted to query all the fields or all the answers from a single field on the form.\u003C/p>\n\u003Cp>This makes that a lot easier as well. So if we go in here, let's just add that corresponding one to many relationship as well. Let's call this answers. Great. And then we've got, what, just like a value?\u003C/p>\n\u003Cp>I'm I'm just gonna simplify this. I'm gonna sort everything as text. You know, if you had different field types or like numbers or dates, maybe you wanna store those in the underlying database as that specific data type. You know, you could set up some different fields for that like value date, value text, value number, etcetera. But to keep this easy we're just gonna store everything as a value.\u003C/p>\n\u003Cp>Alright, so now if we just poke around, right, if I were to like manually add a submission here, I could click create. I'm not seeing the actual answers relationship here. Form answers, form submissions. Oh, that that would be why I'm not seeing it because we haven't created it yet. So the last thing that we need to do is link this form answers to the actual submissions.\u003C/p>\n\u003Cp>So we'll go in and on the form submission, this is going to be a one to many relationship, so we'll call this answers. And the submission ID, great. We'll show a link to that item. Just inspect this. It looks great.\u003C/p>\n\u003Cp>We got a sort field for those answers. Cool. Everything should be gravy. Right? So what's helpful sometimes, especially, like, if you're doing CRUD operations from the front end, maybe I wanna go into this form and I add a new submission manually.\u003C/p>\n\u003Cp>Alright. So just say this. Great. Add some answers. Here's our field that we're answering.\u003C/p>\n\u003Cp>Bryant. Create a new one. Last name, Gillespie. And this is a little bit cumbersome, but, again, we're gonna be handling this from the front end. Gmail.com.\u003C/p>\n\u003Cp>How can we help? This is gonna be what? Sales rep. And then some comments. Comments.\u003C/p>\n\u003Cp>Okay. Alright. So now we've got a submission. I can see kind of the answers here, and these are not super helpful again because we haven't adjusted like the display templates for those. So our form answers, maybe we want to show the the field name, then the actual answer.\u003C/p>\n\u003Cp>Great. Let's set that up. For our form submissions, maybe we want to show the, like, the date this was created, submitted. We wanna show the form as well. So we'll just show form dot title.\u003C/p>\n\u003Cp>And let's just edit this. Underneath the hood, this is just much syntax. We can also do dot notation to access the related fields as well. Set a little bullet point. All right, so now if we go into forms we can see the contact us form submission a minute ago.\u003C/p>\n\u003Cp>And now I can see the individual answers here, which is kind of helpful, right? But let's take a look at the structure of this on the other end. So if I wanted to see an individual submission, I'm just gonna pull this up, copy this, we've got items, form submissions. And then I can get the actual submission here. So here's kind of the structure of this, and this is what we would need to post as well, right?\u003C/p>\n\u003Cp>So we've got a form ID, then we have the individual answers. If I do question mark fields, we do just all the root level fields, we grab the answer fields, see what what kind of thing that we need to pass, right? So we got the field ID that we're gonna need to send via the API request when we submit that. And then we've got the value, so what the value of that is. Okay.\u003C/p>\n\u003Cp>So now let's take a look at actually submitting this form inside here. So let's create a new function. Let's call it Async function. We'll just call it Submit Form. Great.\u003C/p>\n\u003Cp>And what are we gonna do here? We're gonna have, like, a transform the form data to the format you need, submit to the Directus API. Alright. So if we look at our actual form data, how do we get this set up? So it's just an array of answers.\u003C/p>\n\u003Cp>I probably could have made this even simpler than that, honestly, and could have done, what, just the the form field ID. Let's just test this out. Right? Form data dot field. Let's do it this way.\u003C/p>\n\u003Cp>Answers. Form data. We still do answers. Just make that an object, and then we can add the ID for that. Async function.\u003C/p>\n\u003Cp>Oh, misspelled function. Just wondering why that wouldn't compile. Colosbybryant@gmail. Great. And now if I just refresh down here, click form, do we see our form data?\u003C/p>\n\u003Cp>There we go. We've got that. Cool. Alright. So when we submit this form, let's, transform this data.\u003C/p>\n\u003Cp>So, we're going to map through the answers. So we will do data. This is a good ID as well. We're gonna need the form ID. So this will be route dot params.form.\u003C/p>\n\u003Cp>Then we have the answers, but we're gonna need to map those first as well. Map answers. Alright. So object what do we want? Object dot values, object dot keys, form data dot answers.\u003C/p>\n\u003Cp>Yeah. There we go. For each. So we're gonna do the data key. Let's just take a look at our structure here.\u003C/p>\n\u003Cp>We've got the answers. So we need an ID. No. We don't need an ID for the actual answer. Directus should create that for us, but we do need the field ID.\u003C/p>\n\u003Cp>Alright. So we've got answers here. This is gonna be an actual array. And then for that, we're going to do answers dot push field dot key dot value. Answer is key.\u003C/p>\n\u003Cp>Great. Hey. We do console dot log just to test this out. Let's Data to be submitted. Save this.\u003C/p>\n\u003Cp>See what we got, Bryant. Oh, got all this selected. Gillespie. Okay. And then let's add submit or at click submit form.\u003C/p>\n\u003Cp>We refresh really quickly. Right. Gillespie. And now if I hit submit, Form data. Oh, that's a value.\u003C/p>\n\u003Cp>That's reactive. So let's do, like, an on that. Bryant Gillespie. Hit submit. Cannot read properties of undefined.\u003C/p>\n\u003Cp>Field uncaught in promise. Object. Keys. Answers.key. Form data answers.\u003C/p>\n\u003Cp>Console dot log, form data. Let's just unref this form data to see what we're getting. Brandt. Form data. Target.\u003C/p>\n\u003Cp>Answers for each key, data dot answers dot push, field key, form data. Oh, duh. Gotta unref that again. Silly sometimes. Great.\u003C/p>\n\u003Cp>So now I can see here's the data to be submitted. There's the field. There's my value, etcetera. Now all we should need to do is actually send this. So, let's do create item from the Directus SDK.\u003C/p>\n\u003Cp>And just the same way we can fetch those, relationships, those related fields in a single call, we can also push responses to the related fields in a single call as well. So we'll do let's do data. Let's just see it this way. I can't remember what's gonna be coming back. The async data call returns reactive values for data.\u003C/p>\n\u003Cp>But we're not gonna use async data here. This will be just like regular fetch. So we'll do await. Actually we can just use the directus SDK here as well. So we'll do directus dot request, create item, and this will be form_ submissions data.\u003C/p>\n\u003Cp>Is there anything specific we want to return from this? Nope. Let's wrap this in a try catch and just console error any errors that we receive. Form underscore submissions with the data. Console dot log response.\u003C/p>\n\u003Cp>Great. Let's see what we got. Alright. So we're just gonna pick on teammates here. We'll do a Matt Minor.\u003C/p>\n\u003Cp>Maddie atexample.com. I need a developer. Help me, please. Alright. So we hit submit.\u003C/p>\n\u003Cp>Do we see an actual network call going out here? Post forbidden. Oh, no. What are we doing wrong? Right?\u003C/p>\n\u003Cp>Where are we at on time? We got 10 minutes left. We're we're in pretty decent shape. What we don't have here is the permissions to actually create items in form answers and submissions. So we go into our public option here.\u003C/p>\n\u003Cp>We can see that, we've got the ability to read form fields and read forms, but we don't have the ability to create form submissions and create answers. Alright? So there's a couple different ways that you might wanna set this up. You know, if you wanted to, like, potentially route submissions through, directly to the Directus API or, you know, through some other, like, server side redirect, inside Nuxt through like a server route or something. Or in this case, we can just give direct public permissions to create these things.\u003C/p>\n\u003Cp>Now one of the other things that you might wanna do on the read side of it, you know, I I might want to use like a custom permission or something like that so that, people can't read all the actual form submissions. So maybe just the, IDs, etcetera. Or I could even leave this off as well. I'm just gonna enable this just while in testing. Just remember to restrict this before you go to production.\u003C/p>\n\u003Cp>So now if we test this form submission again, what do I get? I get a 200 okay. There's our submission. If I go into Directus, we can see we've got our form submission there. There's all of our answers.\u003C/p>\n\u003Cp>That's great. We don't have the date started because I didn't populate that. Right? But what if we were just to do something like this where we've got the date underscore started, And we do new date. Great.\u003C/p>\n\u003Cp>And this, let's say to ISO string. Just to be sure Directus will swallow that down. Alright. Let's test this one more time and maybe we can fix the field IDX, form field dot link, currentfieldidx. That's where we goofed up here.\u003C/p>\n\u003Cp>Okay. So now we've got Bryant. We got Gillespie. We'll just add a bunch of junk here. Email test at example.com.\u003C/p>\n\u003Cp>I need a developer. Oh, I like 35 submit buttons here. Let's go ahead and submit that down. Submit that up and see if we've got that date started field. Why do I not have that?\u003C/p>\n\u003Cp>Didn't populate. Oh, I didn't actually pass that in there. Date started. Current field, IDX. This actually needs to be just out of here, doesn't it?\u003C/p>\n\u003Cp>Alright. Bryant Gillespie test@example.com. Got a developer. Here's some comments. There's our submit.\u003C/p>\n\u003Cp>Great. Take a look at our submissions. There we go. We've got our date started. So now we could actually, like, run calculations on how long it took to fill out this form.\u003C/p>\n\u003Cp>We've got all of our answers in the correct place. We've got, like, 6 minutes left. Did we check all the boxes here? Right? We've got a form.\u003C/p>\n\u003Cp>We've got the fields. We built the form here on the back end. We've rendered and submitted the form on the front end. Amazing. Let's see if we could just take this a step further.\u003C/p>\n\u003Cp>Maybe we wanna create a dashboard for all of our form submissions. Let's see what we can get here. Maybe we do a simple metric. We want to just track the number of form submissions. So that'll be the field ID.\u003C/p>\n\u003Cp>We'll just do like a count of that ID. Total form submissions. Boom, I can see we've got 4, right? One of the other things that I like to do when building these dashboards inside Directus is make this dynamic. So what I can do is, let's say I wanted to see the answers for a specific form.\u003C/p>\n\u003Cp>What I could do is create a global relational variable. We'll call this, like, form ID. So we'll pick the form. Great. Show that out.\u003C/p>\n\u003Cp>Select a form. See what we got here. I'm just gonna move this out of the way. So we can select a form, that variable is named form ID, and then maybe I want to show a list of responses. So let's show the form submissions.\u003C/p>\n\u003Cp>And for the filter here, what I want to do, let's just show the date that that was submitted. Great. Under the filter section, what I can do here is go in and I'm gonna create a filter that uses that, that variable that we set up. So instead of hard coding a form value here, I could just do something like this where I say form underscore ID equals that variable and just wrap that in the mustache syntax. So responses for this form.\u003C/p>\n\u003Cp>Alright, so when I save this, we shouldn't see any responses here. But if I pick our contact us form, we can see those, then I can open each of those up. Or, you know, we could also change this to something like field ID. So if I wanted to see responses for a certain field, so we'll just change this from forms to a form field. We can go in, the display template for this, maybe we want to show the name of the field.\u003C/p>\n\u003Cp>Maybe we want to show the name of the form that it comes from, the title of that form, where are you. There we go. We don't need a filter there. That's gonna be our field ID. How are we doing on time?\u003C/p>\n\u003Cp>We still got 2 minutes for this. We'll change this to select a field. Field. Great. Responses for selected field.\u003C/p>\n\u003Cp>And then we'll just do the same thing here. We're just gonna change this, right? We'll change the collection to form answers. For our displayed template in this actual list, maybe we want to see the value. So we'll just show the value.\u003C/p>\n\u003Cp>And instead of this, we're gonna do the field dot ID equals field underscore ID wrapped in mustache syntax here. Alright, so now if I select First Name I can see a list of all the responses. Great. Looking great. And then maybe we even duplicate this.\u003C/p>\n\u003Cp>And let's say I wanna show those values in like a chart or something, right? So we got our donut chart here. We got our styles. The field that we want to count in that case is gonna be the value. And now I can get a breakdown of all of those responses.\u003C/p>\n\u003Cp>Great. Cool. 1 minute 24 seconds left. Ring the bell. Again, this has been a fun episode.\u003C/p>\n\u003Cp>You know, taking this from here, obviously, like, I would totally improve the styling, add some validation into it, but this would be super handy to have a form builder that you've got direct control over, that you don't have to pay outrageous prices for, well, like 30 submissions or something like that. I don't know. Let's look at the pricing. What do we have on Typeform? I like Typeform a lot.\u003C/p>\n\u003Cp>The pricing is a bit crazy. Alright. $25 a month gets you or $29 a month gets you a 100 responses. Using Directus, get way more than that for less. Amazing.\u003C/p>\n\u003Cp>Alright. That's it for this episode of 100 apps, 100 hours. I hope you've enjoyed it. I sure have. We'll catch you next time.\u003C/p>","Welcome back to another episode of 100 apps, 100 hours where we try to build or rebuild some of your favorite apps and app ideas in 60 minutes or less or die trying. Emphasis on the die trying. But, if you've not seen the show before, there are basically two rules. You have 60 minutes to plan and build, no more, no less. And the second rule, the anti rule, use whatever you have at your disposal. So today, let's get into the episode, we are going to be building a form builder slash Typeform clone. If you're watching this, you're probably familiar with Typeform, but let's just run through it really quickly. Basically, it is a form builder that, does questions. It's like 1 question at a time. You can customize the design a bit. It kinda looks like this where, hey, we fill out a form. I have to do an actual email here. Then, another question shows up. We go through this. Another question shows up. There's a lot of configurability there, but that's it really. It's a nice looking form builder that that hopefully makes people wanna fill out your forms. That's it. That's what we're gonna build. So let's dive into it. We will put where's my where's my widget? Where's my widget? Timer. Where's the time? Oh, they moved the timer on me. Okay. So we're gonna put 60 minutes on the clock. Let's roll with this. Alright. So let's start kind of planning this out. Right? I always like to start with the functionality. Feature what features do we need? Right? We need to be able to build a form, fields, field types, etcetera. How do I there we go. And then we want to be able to submit that form. Maybe render and submit form. Boom. That's it. Really? That's all the features that we need for this. We wanna build a form, maybe we call this a schema. Then we wanna render the form and submit the form on a front end. Alright. So how do we go about actually doing this? Now, there's a project that I created previously called hgos that has a kind of form builder in there. It is, not the most robust solution. So, the setup there is basically using the JSON repeater inside Directus, which is basically, just an array of form fields that we're gonna use. We pass that JSON to our component on the front end that renders a form dynamically based on the different types of inputs that we want. And then when you submit that, it all goes into a single table, into a single collection. So that is super flexible. And and if all you need to do is, output a a JSON schema to create a form and have a single collection, where you store all that data as JSON, great. Let's take this a step further in this episode though. And let's add this, make this a little more robust. So when I think about the data model, we're going to have a form or forms. Each one of those forms will probably have some form fields. Great. And then on the submission side, we've got like a form submissions. And within that, we have an ans we have answers, I guess. And so we've got the form submissions belong to the forms, the answers belong to the form submissions. Just kind of drawing these things out. There'll be a relationship between the answers and the form fields. I skipped the magic arrows here. Great. There we go. So this is kinda the way I've got it in my mind. We've got a form that holds all of the form fields. There's probably a title. We've got like a redirect or or some kind of on submit action, what we wanna do with it. On the front end, we fetch that form and all the form fields, we render it. When somebody fills out that form, we create a form submission, and that form submission is made up of answers and probably some metadata about when that form was submitted, who's submitting it, etcetera. That sounds like a good start for our planning. I'm just gonna throw these up side by side so I don't forget. And to start this out, I've got my blank instance of Directus, that's what we're using on the back end here. We're just going to hit create a new collection. Let's call this Forms. Now I'm gonna use the UUID for this. Maybe we just do the standard format here. User created, date created, date updated. I'm gonna wanna know what the status of these forms is. Do we need a sort for the forms? Maybe. Do we need a status? We'll just go ahead and add it. Alright, so let's give the form a title, maybe a form description, so that'll be a text area. And then we're going to create another collection called Form Fields. So we'll just go in, create a new collection. What's happening behind the scenes, obviously Directus is creating these tables in MySQL database. The fields, we definitely need to sort on the fields. Do we want a status on those? Maybe, maybe not. No. Let's keep status off of this. You know, do we want to know when the actual form fields were updated? Maybe I'm not super concerned with that, so we'll just leave those off. All right. So for the form fields, when we're thinking through this, right, what are we gonna need on our actual form fields? We're gonna need a name for the field. So what's the field name? We're gonna need a type for the field. So the type is probably gonna be a string, but maybe as far as, like, the interface that I wanna use, I want this to be a drop down. So we can think about input. Great. Do we have a, like, a form field for that or an icon? Yeah. Let's try that. Let's do text area. Text area. Text area. Do we have one for that? We don't. We'll just go with that option. What else do we wanna display? Like a select? Just thinking along the lines of the actual HTML form elements we're gonna use here. Drop down, maybe select. Good looking icons are sparse here. Okay. So that's probably good for now. We'll just make this a label, format each label, we don't want to show as a dot. I can actually copy and paste the choices for our interface into the display here. And this is what shows up on the, like, the index pages where you could potentially see a list of the form fields. Alright, so we've got a name, let's make this half width, make it look good when we're actually creating our form. We've got a name, I could double as label. Trying to think of what else we've got on this. Maybe some type of validation name and type. If it is a select type, we probably need, like, some available choices. Right? So let's go ahead and do that. We'll do that as the, like, JSON repeater. We'll call this select options, I guess. Great. We'll add a text field. So this is the text we're gonna display or we use kind of the label value format. So we set that up, that'll be an input, very simple. And then we have a value. In case we wanna store those differently, we can require a value, make that half width as well. Great. And let's add some conditionality to this that, if the type is select that's when we show this. So I'm gonna go into our field options, let's just hide this by default, and then inside Directus I can add conditionals to the forms inside the Studio by doing something like this. So we'll say show if type equals select. And this is just a description so I can keep my logic together. We'll say if the type equals select we're going to make sure this is not hidden, and that should give me what I am looking for. Right? We could expand this further and add, like, conditions to our front end and etcetera. What we need to do now, though, is add the relationship between those. So in this case, we want a form field. It should only belong to a single form in my mind. I I you could I guess you could make the argument either way, but in this case, let's just create that relationship. So this is gonna be a one to many relationship here. So this is gonna be called Fields, and I'm inside our forms collection, our forms table right now. I'm just going to call this key fields and we're going to look, the related collection is our form fields. And the foreign key inside the form fields table, I'm going to create a new field called form that relates back to the actual form. Gravy will show a link to the item. And if I open up this advanced field creation mode inside Directus, I get a lot more options that I can configure and play with. So I'm going to add a sort field, we're just going to make sure that sort is selected here. That allows us to drag and drop the order of those fields. And then for our triggers, like if we deselect a form field, do I want to delete that item or not? In this case I'm just gonna nullify the form field. Great, we'll display the related values. Bada bing bada boom. Now we should have a form with some fields. Let's just test this out really quickly. So we're gonna go in, we got a form. This is our contact us form, the most standard of all forms on the Internet. And here's the standard contact us form. Great. Alright, so we'll do name, is gonna be email, this will be an input. Great. What are some of the other things that we'd probably want to add to this? Like a placeholder, I would say. First name, that'll be an input. What else? Last name. Okay. Comments, that'll be maybe a text area. And now let's, let's get into the select option. Right? How can we help you? Alright. So for the options here, I need a developer, so we'll give it a label. Maybe we store the value as just developer. I need a sales rep. I don't know. Great. Okay. So a couple of things here. I don't see the actual form values that I I might look. You know, here's they're just showing IDs. How can I fix that, right? I want to make sure this looks nice when we're setting these things up. So what I can do, if I go into my form fields, we have the display template. So this is the default anytime we're referencing this form field. Or I could go into forms and go to the fields section here and I can control the display template just within forms. So that's super handy if you, need different display templates in different places, but in this case let's just go and edit the default one. Let's do the name and then we'll maybe put like a special little dot there and maybe we want to show the type of field that is. And now if we just go back to our forms, great, we can kind of see what we're working with here. I don't really let's put first name, last name, then email, then comments, and how can we help you? Maybe we move that up above. Right. Great. Alright. So now we've got the start to this form. Let's go in and actually start messing with the front end. We've got 47 minutes on the clock. I'm just gonna pull this up. Let's pull up my Nuxt application. This is just the standard Nuxt application that I have out of the box. That's my weapon of choice in this case. And let's just go in, I'm going to create a new directory in the pages directory. So this will give us a route automatically on the front end. I'm gonna call this forms, and then I'm gonna do just form in brackets here, and that will give us, like, a a dynamic form. If I do view script setup, TS setup, this is kind of what I want. Great. We've got the standard Vue Form Component. And we'll just say Form Component. Great. Alright. So let's test this out on the frontend, forms. Test. What? Uh-oh. Some type of issue going on. There it is. Form component. And this has got a layout attached to it where we're showing, like, the logo and things like that. Maybe not a huge issue for this, but, you know, the type form has, like, this full screen form effect, so, you know, maybe we dive into that later. Alright. So now I actually want to fetch that form from Directus, right? So a couple things, we probably need to edit our access control. So by default, Directus locks down every single thing inside your instance. Only admins are able to, interact with the API by default. So we're just going to go in. I'm gonna set readability for our forms and our form fields, and now if I just wanted to test that out quickly, I could do something like this in the browser where I go to my directus url/items/forms, and I can see I get the form data here, and and we'll expand that, once we dive into the Nuxt side of things. Right? So the first thing that we want to do here, we're gonna get the routes, so we know which form to fetch. And then we're gonna be using the Nuxt Async Data Call. So this Nuxt application, I've got a Directus plug in here that basically fetches information from the Directus API. We provide the SDK to the Nuxt application so I could do things like this where I just simply say give me the Directus client. And that way it's shared across the actual Nuxt application. So I get my Directus client, and then we're gonna use the, let's say data is coming back. We're gonna use the Async data call from Nuxt. And within that we are going to return Directus dot request. And then we need to import the readitem from the Directus SDK. Alright. So this is just going to read a single item. We're gonna pick that up from the actual form, from the route itself. So we'll say directives dot request. It'll be read item. We're reading items from the form collection. And then we've got route dot params dot form. Right? So that will it should give us the form. And then I can pass a a set of parameters to the Directus API to get things like form fields, etcetera. But let's just take it from here. We'll wrap this in another div. I always end up doing this just to take a look at my data and just wrap it in a pre tag. And now if I do forms test, we're probably not gonna get back any data. If I was to open this up, I don't really see anything here. Great. Alright. So now let's actually test this with the ID of one of the forms. We'll go in. Great. Okay. Looks like we are getting a cores error. So I've goos something up in my direct to settings. CORS enabled. CORS origin. Should be interesting here. Let me restart this Docker container and see if that solves my problem. If I look at my ENV, I've got HTTP local host 8055. That is that's where my direct instance is running. Okay. There we go. I just needed to set the cores in this case to make sure that local host 3,000 is allowed, and basically, I've done that the quick and dirty way just by allowing any origin here. But, typically, I would set this to, like, local host or something like that. Alright. So we could see here's our form data. That's great. How do we actually get the form fields? One of the nice things about the Direct Express API is the ability to call this data in a single API call to expand those relationships, right? So inside my form fields, maybe on the front end, I don't really care about this extra data when it was created. So let's just say we want the ID, we want the name of the form, we want the oh, it's actually the title of the form. Title. Great. And we want the description. And then we get into the fields. All right, so I could do something like this where I add a dot notation and a wildcard which will give me all the items for that field or all the all the fields within the fields collection or the the related items there basically. So instead, what I'm gonna do, if you're using the SDK, we could pass it a syntax like this where we say fields. And here, we're gonna specify the actual fields. We want the name. We want the type. We want the options, and that should give us what we're looking for here. Right? And we probably do need the ID of those fields as well. We'll look at submissions in a few. Okay, so now we've got these fields coming in, this looks good, we've got a type, we've got a name for these, we can start to construct like a form with this actual data. Now on the front end in this Nuxt application, I have this Nuxt UI library. It would be suicide to try to build this without a form library, ahead of time here or, like, some type of UI library so I could do forms. They've got this really nice setup where you can pass it a form component. They've got Zod validation, support for Yelp and Joy if you're into that. But looking at this, everything gets wrapped in this form group component, which gives you like a nice label, etcetera. So let's just try to iterate through this. Right? We've got a form. They have a u form component. Not concerned with, like, validation at this moment. Let's just display a form. Right? So we're gonna go through Uform group, v 4 fields and forms dot fields. The field, we wanna pass like a I think there's a name component for each one of these. Like, if we go in and look at the props, we gotta pass a name to it. So we'll do name. It's field dot name. Great. What else do we need here? So within the form group, then we are going to do the actual component. So we've got an input here. There's a select box. Great. What else do we have? We had text area. Right? So what I'm going to do, I'm just gonna create a map here, basically. So we'll do, a field map. And I'm just gonna do something like this where, that actually looks pretty good. You input, you text area. That's how all these are name spaced. But what I wanna do instead is use this, like, resolve component or, you know, I could even actually import these as well. So this resolve component is just a helper. You could just straight import them as well. And with Nuxt, you can actually load these dynamically as well by adding lazy in the front of them. So it only pulls in that, that component when it actually needs it dynamically. Alright. So we've got our fields, we've got our form groups within the form, and GitHub Copilot is helpful again in this aspect that we want to do the field dot type inside. We wanna pull that correct component from our map, give it a name, give it an options. Let's take a look. Options are there. Name and value. Okay. So let's save this, see what we get. It looks like we have some we got some form of something going on. I need to change this right away to input so we can actually see those. Let's give this a little bit of styling. Right? Maybe we do, like, a max width XL. Format with math width Excel MX Auto. Great. Maybe we make this a grid. We give it some gap. Why is this actually not should be parsing out my is Tailwind not included in this? What what have we got going on? My styling is not being applied from Tailwind. That's kind of upsetting. We can definitely sort that out though. And then we need a label for this as well. So we'll do the field dot name for the label. Okay, so now we've got a kinda set up in there. Let's do view button. And we'll do, like, a submit option here. Let's add that outside the form group. So we get a submit button at the bottom of this. Right? Okay. So how can we make this kind of like typeform in a way because we wanna answer these one question at a time? Or how can we kind of loop through these? Right? Gillespie. Great. We don't have a state for these, so we'll we'd need to update that as well. Let's do v model equals set up, like, our form data. Form data, maybe we use like a ref for this. It'll be form data, and we can dynamically do field dot ID. Let's put this in like an answers array. Answers. And then we're gonna have the field dot ID. Okay. Alright. And within this, I'm just gonna do answers, and we'll just preset that. All right. So now if I take a look at like my Vue Dev Tools and we go to Form, Okay. I can see my Directus object. I've got my form data. There's my answers. Now if I populate these, is that actually doing what I want? More data. Click form. Do we see our answers? Oh, maybe we want to grab the index of this as well. Field index inform dot fields. That could be helpful. Answers field index field dot id. Bryant Gillespie. Bryantdirectus.io. Let's take a look at the data we're pushing here. Okay. So I can see my fields now. Oh, nope. Form data. There's my answers. Okay. That'll that'll suffice for now. All right. So let's do these one at a time as well, right? So maybe we give this a const current field idx. We set that to 0. And then, how are we gonna want to display this? Alright. So we're gonna render each field conditionally. Alright. So let's just wrap this in a template. This will be the what are we gonna do here? So we still need to show the actual field. V. This could actually be something like this, I think. V if current field current field IDX equals field IDX template. We'll loop for these Fields. Field IDX, inform dot fields key equals field dot field. Nope. Field.id. Alright. Let's see what we've got here. Oh, don't wanna close. Let me open this up. Alright. V field dot ID. We don't need a key on that one. V4fieldsfield. Alright. So now we've got kind of this one field for each. One of the cool utilities that I use a lot is also the auto animate by a group called FormKit. It's, v autoformkit auto animate. One line of code or just a couple lines of code and you've got, like, nice form animations, for, like, list transitions, showing and hiding inputs, etcetera. And we're only gonna show the button if fields, field idx is equal to form dot fields dot length. Field.ex +1 is equal to form.fields.length. So here we shouldn't be showing that button. Give us a padding here. Cool. Alright. Now within this, we probably need, like, a previous and next button as well. So let's just do, like, a flexbox, Go to some gap. We'll do U button. Next. Okay. GitHub Copilot. Alright. So basically, if the current field IDX is greater than 0, we're going to display the previous button. If the current field IDX is not greater than the end of the form, we want to show that. But we need to fix the actual setup here. You form group. Maybe we wrap this in a div. Okay. So now you could see we could kinda go through each one of these. And do I have the actual auto enemy applied properly in this case? Via, it should be. It's not actually animating. Let me refresh. Ah, there we go. Now we're getting some nice animation or animations. Right? So we'll do some margin there. Add justification between these. And we do one of these as variant equals soft. Is that what it is? There we go. Alright, so this is the primary. Great. There we go. Cool. Alright. So now I could go through and fill out this form. Alright. Oh, that's actually submitting the form. I don't wanna do that. Alright. Next. Gillespie. May we change this to button? Great. Give him my email. Cool. Starting to look something like Typeform in this case. Alright. So test, test, test. V if field equals fields dot length. Oh, maybe this is the actual code that we need here. Why are we not seeing the so, oh, because it's not in that actual loop. There we go. So now we should see the submit button. Great. We don't have any type of handler on the submit button. And it's also not doing anything if we actually, were to submit this anyway because we've got, we we don't have anywhere to post it inside Directus. Right? We've got roughly 20 some odd minutes. Let's make this form actually submit. Right? So if we go back to our setup, right, we've got, a form submission, we've got some answers that are within that. So let's go ahead and set up those collections. Let's say form submissions. These will be DUID. This will be let's call this submitted, date submitted. That kinda matches what we've got already. Yeah. Maybe we wanna track the time that it actually user created, date updated, user updated, the status of this submission. You know, you could even like, building your own here, you could certainly, like, track partial submissions and things like that as well if you wanted to. Like, we could submit any time and then update whenever they submit the final one, sort of thing. But let's say we have, like, a date started type of thing. Maybe we wanna, like, store that whenever they start this form submission. So we could track things like how long it actually took them to do this. For that we want the date time. We're just gonna do time stamp. Cool. Day started. Date submitted. Let's make sure we show that field. I'm just going to make one of these full width so I can get what I want out of this. Okay. Alright, so the form submission, we've got to link that back to an actual form as well. So we will do a mini to 1 here. This will be our forms. And let's do forms as the related collection. Now if I open up advanced mode, I can also create the inverse relationship as well. So if I wanted to do something like this where inside forms I've got a relationship back to the submissions, which in this case makes a lot of sense because of probably want to see the submissions. We could set up that and create that relationship in a single go. So there's our form. And then the next thing that we want to do is create a form, I'm gonna call it form submission answers. It could just be form answers, I guess. Always like to, have like a a common prefix, among these things, especially when you start building projects that have a ton of tables in them. Alright. So there's probably a sort on that specific answer. Do we need any of these additional fields? Probably not, right? So when I think of the answer, we've got a relationship back to the form field. So we'll just call this field. Let's Create that. There's the form fields collection. And again, I could do the same kind of thing. And setting this up this way, you know, with all the different database tables and the collections inside Directus allows us to, you know, say I wanted to query all the fields or all the answers from a single field on the form. This makes that a lot easier as well. So if we go in here, let's just add that corresponding one to many relationship as well. Let's call this answers. Great. And then we've got, what, just like a value? I'm I'm just gonna simplify this. I'm gonna sort everything as text. You know, if you had different field types or like numbers or dates, maybe you wanna store those in the underlying database as that specific data type. You know, you could set up some different fields for that like value date, value text, value number, etcetera. But to keep this easy we're just gonna store everything as a value. Alright, so now if we just poke around, right, if I were to like manually add a submission here, I could click create. I'm not seeing the actual answers relationship here. Form answers, form submissions. Oh, that that would be why I'm not seeing it because we haven't created it yet. So the last thing that we need to do is link this form answers to the actual submissions. So we'll go in and on the form submission, this is going to be a one to many relationship, so we'll call this answers. And the submission ID, great. We'll show a link to that item. Just inspect this. It looks great. We got a sort field for those answers. Cool. Everything should be gravy. Right? So what's helpful sometimes, especially, like, if you're doing CRUD operations from the front end, maybe I wanna go into this form and I add a new submission manually. Alright. So just say this. Great. Add some answers. Here's our field that we're answering. Bryant. Create a new one. Last name, Gillespie. And this is a little bit cumbersome, but, again, we're gonna be handling this from the front end. Gmail.com. How can we help? This is gonna be what? Sales rep. And then some comments. Comments. Okay. Alright. So now we've got a submission. I can see kind of the answers here, and these are not super helpful again because we haven't adjusted like the display templates for those. So our form answers, maybe we want to show the the field name, then the actual answer. Great. Let's set that up. For our form submissions, maybe we want to show the, like, the date this was created, submitted. We wanna show the form as well. So we'll just show form dot title. And let's just edit this. Underneath the hood, this is just much syntax. We can also do dot notation to access the related fields as well. Set a little bullet point. All right, so now if we go into forms we can see the contact us form submission a minute ago. And now I can see the individual answers here, which is kind of helpful, right? But let's take a look at the structure of this on the other end. So if I wanted to see an individual submission, I'm just gonna pull this up, copy this, we've got items, form submissions. And then I can get the actual submission here. So here's kind of the structure of this, and this is what we would need to post as well, right? So we've got a form ID, then we have the individual answers. If I do question mark fields, we do just all the root level fields, we grab the answer fields, see what what kind of thing that we need to pass, right? So we got the field ID that we're gonna need to send via the API request when we submit that. And then we've got the value, so what the value of that is. Okay. So now let's take a look at actually submitting this form inside here. So let's create a new function. Let's call it Async function. We'll just call it Submit Form. Great. And what are we gonna do here? We're gonna have, like, a transform the form data to the format you need, submit to the Directus API. Alright. So if we look at our actual form data, how do we get this set up? So it's just an array of answers. I probably could have made this even simpler than that, honestly, and could have done, what, just the the form field ID. Let's just test this out. Right? Form data dot field. Let's do it this way. Answers. Form data. We still do answers. Just make that an object, and then we can add the ID for that. Async function. Oh, misspelled function. Just wondering why that wouldn't compile. Colosbybryant@gmail. Great. And now if I just refresh down here, click form, do we see our form data? There we go. We've got that. Cool. Alright. So when we submit this form, let's, transform this data. So, we're going to map through the answers. So we will do data. This is a good ID as well. We're gonna need the form ID. So this will be route dot params.form. Then we have the answers, but we're gonna need to map those first as well. Map answers. Alright. So object what do we want? Object dot values, object dot keys, form data dot answers. Yeah. There we go. For each. So we're gonna do the data key. Let's just take a look at our structure here. We've got the answers. So we need an ID. No. We don't need an ID for the actual answer. Directus should create that for us, but we do need the field ID. Alright. So we've got answers here. This is gonna be an actual array. And then for that, we're going to do answers dot push field dot key dot value. Answer is key. Great. Hey. We do console dot log just to test this out. Let's Data to be submitted. Save this. See what we got, Bryant. Oh, got all this selected. Gillespie. Okay. And then let's add submit or at click submit form. We refresh really quickly. Right. Gillespie. And now if I hit submit, Form data. Oh, that's a value. That's reactive. So let's do, like, an on that. Bryant Gillespie. Hit submit. Cannot read properties of undefined. Field uncaught in promise. Object. Keys. Answers.key. Form data answers. Console dot log, form data. Let's just unref this form data to see what we're getting. Brandt. Form data. Target. Answers for each key, data dot answers dot push, field key, form data. Oh, duh. Gotta unref that again. Silly sometimes. Great. So now I can see here's the data to be submitted. There's the field. There's my value, etcetera. Now all we should need to do is actually send this. So, let's do create item from the Directus SDK. And just the same way we can fetch those, relationships, those related fields in a single call, we can also push responses to the related fields in a single call as well. So we'll do let's do data. Let's just see it this way. I can't remember what's gonna be coming back. The async data call returns reactive values for data. But we're not gonna use async data here. This will be just like regular fetch. So we'll do await. Actually we can just use the directus SDK here as well. So we'll do directus dot request, create item, and this will be form_ submissions data. Is there anything specific we want to return from this? Nope. Let's wrap this in a try catch and just console error any errors that we receive. Form underscore submissions with the data. Console dot log response. Great. Let's see what we got. Alright. So we're just gonna pick on teammates here. We'll do a Matt Minor. Maddie atexample.com. I need a developer. Help me, please. Alright. So we hit submit. Do we see an actual network call going out here? Post forbidden. Oh, no. What are we doing wrong? Right? Where are we at on time? We got 10 minutes left. We're we're in pretty decent shape. What we don't have here is the permissions to actually create items in form answers and submissions. So we go into our public option here. We can see that, we've got the ability to read form fields and read forms, but we don't have the ability to create form submissions and create answers. Alright? So there's a couple different ways that you might wanna set this up. You know, if you wanted to, like, potentially route submissions through, directly to the Directus API or, you know, through some other, like, server side redirect, inside Nuxt through like a server route or something. Or in this case, we can just give direct public permissions to create these things. Now one of the other things that you might wanna do on the read side of it, you know, I I might want to use like a custom permission or something like that so that, people can't read all the actual form submissions. So maybe just the, IDs, etcetera. Or I could even leave this off as well. I'm just gonna enable this just while in testing. Just remember to restrict this before you go to production. So now if we test this form submission again, what do I get? I get a 200 okay. There's our submission. If I go into Directus, we can see we've got our form submission there. There's all of our answers. That's great. We don't have the date started because I didn't populate that. Right? But what if we were just to do something like this where we've got the date underscore started, And we do new date. Great. And this, let's say to ISO string. Just to be sure Directus will swallow that down. Alright. Let's test this one more time and maybe we can fix the field IDX, form field dot link, currentfieldidx. That's where we goofed up here. Okay. So now we've got Bryant. We got Gillespie. We'll just add a bunch of junk here. Email test at example.com. I need a developer. Oh, I like 35 submit buttons here. Let's go ahead and submit that down. Submit that up and see if we've got that date started field. Why do I not have that? Didn't populate. Oh, I didn't actually pass that in there. Date started. Current field, IDX. This actually needs to be just out of here, doesn't it? Alright. Bryant Gillespie test@example.com. Got a developer. Here's some comments. There's our submit. Great. Take a look at our submissions. There we go. We've got our date started. So now we could actually, like, run calculations on how long it took to fill out this form. We've got all of our answers in the correct place. We've got, like, 6 minutes left. Did we check all the boxes here? Right? We've got a form. We've got the fields. We built the form here on the back end. We've rendered and submitted the form on the front end. Amazing. Let's see if we could just take this a step further. Maybe we wanna create a dashboard for all of our form submissions. Let's see what we can get here. Maybe we do a simple metric. We want to just track the number of form submissions. So that'll be the field ID. We'll just do like a count of that ID. Total form submissions. Boom, I can see we've got 4, right? One of the other things that I like to do when building these dashboards inside Directus is make this dynamic. So what I can do is, let's say I wanted to see the answers for a specific form. What I could do is create a global relational variable. We'll call this, like, form ID. So we'll pick the form. Great. Show that out. Select a form. See what we got here. I'm just gonna move this out of the way. So we can select a form, that variable is named form ID, and then maybe I want to show a list of responses. So let's show the form submissions. And for the filter here, what I want to do, let's just show the date that that was submitted. Great. Under the filter section, what I can do here is go in and I'm gonna create a filter that uses that, that variable that we set up. So instead of hard coding a form value here, I could just do something like this where I say form underscore ID equals that variable and just wrap that in the mustache syntax. So responses for this form. Alright, so when I save this, we shouldn't see any responses here. But if I pick our contact us form, we can see those, then I can open each of those up. Or, you know, we could also change this to something like field ID. So if I wanted to see responses for a certain field, so we'll just change this from forms to a form field. We can go in, the display template for this, maybe we want to show the name of the field. Maybe we want to show the name of the form that it comes from, the title of that form, where are you. There we go. We don't need a filter there. That's gonna be our field ID. How are we doing on time? We still got 2 minutes for this. We'll change this to select a field. Field. Great. Responses for selected field. And then we'll just do the same thing here. We're just gonna change this, right? We'll change the collection to form answers. For our displayed template in this actual list, maybe we want to see the value. So we'll just show the value. And instead of this, we're gonna do the field dot ID equals field underscore ID wrapped in mustache syntax here. Alright, so now if I select First Name I can see a list of all the responses. Great. Looking great. And then maybe we even duplicate this. And let's say I wanna show those values in like a chart or something, right? So we got our donut chart here. We got our styles. The field that we want to count in that case is gonna be the value. And now I can get a breakdown of all of those responses. Great. Cool. 1 minute 24 seconds left. Ring the bell. Again, this has been a fun episode. You know, taking this from here, obviously, like, I would totally improve the styling, add some validation into it, but this would be super handy to have a form builder that you've got direct control over, that you don't have to pay outrageous prices for, well, like 30 submissions or something like that. I don't know. Let's look at the pricing. What do we have on Typeform? I like Typeform a lot. The pricing is a bit crazy. Alright. $25 a month gets you or $29 a month gets you a 100 responses. Using Directus, get way more than that for less. Amazing. Alright. That's it for this episode of 100 apps, 100 hours. I hope you've enjoyed it. I sure have. We'll catch you next time.","published",[135],{"people_id":136},{"id":137,"first_name":138,"last_name":139,"avatar":140,"bio":141,"links":142},"791e1503-1d88-463d-9347-0b9192933576","Bryant","Gillespie","9013afc8-e8d7-4182-9b18-44db08117bb9","Developer Advocate at Directus",[143,146],{"url":144,"service":145},"https://directus.io/team/bryant-gillespie","website",{"service":147,"url":148},"github","https://github.com/bryantgillespie",[],{"id":151,"number":152,"year":153,"episodes":154,"show":164},"14fda5f2-95de-4dbe-a4e2-3fd956c21c19",2,"2024",[155,156,157,158,159,160,161,162,163,122],"9a3a8ffa-a27b-421c-93cf-3da2dcb726e9","d072a935-906e-4208-a5dc-e9b117d0ab29","b9f1d4cf-f53c-49db-9e87-adf7e3b9ff99","aad8d674-2b58-4604-8e43-b98f7c6e05cb","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",{"title":165,"tile":166},"100 Apps In 100 Hours","fb0f9d45-be21-4634-94d4-2ef1cc5146f2",{"title":8,"meta_description":8},{"id":169,"slug":170,"season":171,"vimeo_id":172,"description":173,"tile":174,"length":175,"resources":8,"people":8,"episode_number":176,"published":177,"title":178,"video_transcript_html":179,"video_transcript_text":180,"content":8,"seo":181,"status":133,"episode_people":182,"recommendations":184},"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",[183],"cc9262db-1ae2-4bc1-a1f9-7d2fc51a396d",[],{"reps":186},[187,243],{"name":188,"sdr":8,"link":189,"countries":190,"states":192},"John Daniels","https://meet.directus.io/meetings/john2144/john-contact-form-meeting",[191],"United States",[193,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],"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":244,"link":245,"countries":246},"Michelle Riber","https://meetings.hubspot.com/mriber",[247,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,224,435,436],"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",1773850453299]