In this chapter, we're going to implement a widget customization form. Let's start by fixing the useVapiData hooks from the previous chapter. If you remember, CodeRabbit left a useful comment here. One for adding the cancelled variable so that we clean up async operations and prevent memory leaks. We have to do this for both of our hooks.
And the other one was to remove the get assistance and get phone numbers dependency since that could potentially cause an infinite re-render. So I want to make sure we don't run into those issues first. I'm going to go inside of web and let's go inside of our modules, plugins, hooks, use WAPI data. So first things first, let's remove the get assistance from here and let's remove get phone numbers from here. Now let's go ahead and let's add this.
So I'm going to add on the example of use WAPI assistance here inside of the use effect. Let me just see. Let's go ahead and inside let canceled to be false. And then go ahead instead of try after we successfully get the result. If canceled, let's go ahead and do an early return.
And I prefer writing if clauses like this. I don't like the inline ones. In the error, let's do the same thing in the beginning here. Basically, why are we doing this? The point is not to do this after a wait.
The point is to do this before we set data or before we set error. Because error or data are the results of an asynchronous operation. Set is loading is not a result of an asynchronous operation. So we only have to do it to prevent setting state on a component that has unmounted. That's why we are doing this.
And in the finally, let's go ahead and only set this if not cancelled. So if we are still continuing to fetch this as expected. And finally, let's simply just add an unmount function here, which will set the cancelled to be true, I believe. Let's see. Exactly.
Yes. And that's how you safely clean up your functions here. Now let's do the exact same thing. We just did it for use of AP assistance. Now is it for use of AP phone numbers.
So inside of use effect here instead of use WAPI phone numbers we set let cancelled to be false. After that before we set the result let's go ahead and early return if we have cancelled this method. Let's also do an early return before we throw an error here. And let's only set this loading if we have not cancelled. Like so.
And then in here let's return cancelled true. There we go. Now both of our hooks here should be more robust, they shouldn't cause infinite re-renders, and they should be cancelable, meaning that we don't have to worry about memory leaks. This is actually quite important to do, and this is why people always choose to use react, react query hook. Did I react query?
That's the name of the package because they handle that internally. We could technically add it here as well, but I don't know. We need to add the whole provider and everything and it's only these two examples where we need it. For other places we can use the use query from convex so I'm not 100% sure if it's worth it. And yeah for this get assistance Yeah I'm not 100% sure if that's memoized or not because if it's not then yes it will cause infinite re-renders here.
Same thing for get phone numbers. I have a feeling convex thought of this and they would make it memoized but just in case yeah we technically don't need it in the dependency array it will still be accessible here it just won't update so save this let's go ahead and run turbo dev and let's go inside of our web dev and let's go ahead and refresh our plugins WAPI here. And there we go. Phone numbers are loading, assistance are loading and I don't see any problems in here. Excellent.
And if you accidentally switch while something's loading, you won't get that overflow, right? So this is the problem, right? Imagine if this voice assistant took you five seconds to load, and in the middle of those five seconds, you switch to knowledge base. What would happen is that, for example, this hook will try to set data and then try to use it, but it was unmounted at this point. That's why we had to add the canceled to fix that potential issue.
Excellent. Now let's go ahead and let's build this, the widget customization. And in here, we're gonna be able to basically allow user to select their chosen phone number and their chosen assistant, which is what we actually tell them right here, like go inside of your settings if you want to add that. But before we can do that, we actually have to go inside of our convex schema. And inside of schema, we actually have to add widget settings.
So let's go ahead and add widget settings, define table. Let's go ahead and start by adding the organization ID, which is a type of string. Let's add greet message, which will basically be the message that your chatbot will greet the user with. And let's add the default suggestions. This will be a type of object.
And let's add suggestion one to be an optional string. And then do that two more times. So we are going to allow a very simple structure to allow three suggestions. If you want to, you can modify this to be infinite suggestions, But honestly, only a small number of this looks good in a chat box, right? I don't know if it makes sense to add more than three.
And let's also do WAPI settings here, which is an object, assistant, ID, optional, string. Phone number. Whoops. Optional string. There we go.
And now we have the widget settings that we can modify and load later on. And that's how we're going to allow an organization to customize their chat box widget. For example, if they want their chat box AI to greet users with Hi there, they will be able to modify. If they want hello, they will write hello. And some default suggestions, like commonly asked questions or things like that.
And in here, pretty logical, the assistant ID and the phone number they want to use. After that is done, as always, double check in your workspace backend that you have that schema updated, that the functions are ready. Great. And one important thing here is to add an index. So index by organization ID.
And let's use the organization ID field. That's the only index we need. Make sure that this adds an index here. Let's just wait a second. There we go.
Index has been added. Excellent. Now let's go ahead and let's create the customization view. For that we're going to go inside of apps, web, modules and let's create a new module called customization. Inside Let's go ahead and UI, Views, Customization, View.tsx.
Let's mark this as Use Client. Let's export Const, whoops, Customization View. And let's return a div, Customization View. Then let's go ahead inside of Apps. Let's go inside of Dashboard, Customizations, CustomizationPage.
And in the return, CustomizationView. As simple as that. And now in the widget customization you should see Customization view component being rendered. And now we can focus on that right here. So the first thing I'm gonna do is I'm going to get my widget settings using use query from convex react and API from workspace backend generated API.
And in, Oh, I actually didn't create the functions, so we can't do anything. But yeah, now we have to create the widget setting functions. I went ahead of myself and created the frontend. But yes, we have to go inside of backend, We have to go inside of convex, private, and let's create widget settings.ds. And let's go ahead and do export const get one query.
Let's import the query. Arguments are going to be empty. Handler is going to be an asynchronous method. Inside of here as always we have the context. We don't need arguments since we don't have them.
Now let's go ahead and just do our usual identity check here and organization check. Where are my widget settings? Here they are. So confirm that the user is logged in and the user has active organization. If you've created a util, feel free to use that util, right?
And now let's go ahead and very simply do const widget settings. And let's do await context.database query widget settings with index by organization ID query query equals organization ID organization ID and return widget settings. Just like that. Make sure your backend is running so you have those new changes. And now in your customization view you can use API, private, widget settings, get one and actually, yeah, no arguments needed because you can only fetch your current organization settings.
It's not externally controlled. And now in here, let's go ahead and do JSON stringify widget settings. Oh, we have some errors here. Yes, we have some errors. In the widget settings, we need to also add unique here.
My apologies. After you did that, your functions should now work. There we go. Now let's go ahead and refresh to see if the errors got fixed and there we go. By default it's null because, well, I have no widget settings available at the moment.
Great. So now what I want to do is I also want to fetch my WAPI plugin but actually I might want to leave that for later because right now in this chapter I only want to focus on building the settings that we just defined in the schema, then later we're going to add WAPI plugin as well. Let's go ahead and check if widget settings is equal to undefined. And in that case, let's go ahead and return a div, class name, minimum height of screen, flex, flex column, items center, justify, center, gap, y2, background muted and padding 8. Inside of here let's add loader2 icon with the class name, text muted foreground and let's do animate spin and let's add a paragraph loading settings and let's give this a class name text muted foreground and text small And now when you refresh this page for a brief second here, you should see loading settings like that.
Excellent. And now that we have our loader screen, let's go ahead and let's add the actual component here. And now we're going to add to this div a class name flex minimum height of screen flex column bg muted and padding 8. Instead of JSON stringify widget settings, let's go ahead and let's limit how wide this screen will go using maximum width screen md, mx auto and full width. Now inside of here let's add a div.
Let's go ahead and give it space y2. And we're now going to use the exact same thing that we did inside of our, let's see, wapi-view I believe. So head inside of your WAPI-view and in here scroll down, make sure that you are looking at the WAPI-view component and In here we have the same thing, you see. H1 and a paragraph inside. You can copy that.
That's the div. And now you should see WAPI plugin here. Now let's just change the text. So this will be widget customization and in the paragraph here, let's see, customize how your chat widget looks and behaves for your customers. There we go.
Now outside of this div, let's create a new one. Let's go ahead and give it a class name, margin-top of 8. And inside of here, customization-form. And inside of the customization-form here, Let's pass in initial data to be widget settings. Like this.
Instead of your customization plugin, UI folder, go ahead and create components. And inside of here, create customization-form.tsx. Now inside of here, let's prepare the usual. Well let's also add use client here. Actually we don't need to add use client because the view itself has use client.
So let's import Zod resolver. Let's go ahead and import z from Zod. Let's import use form from react hook form. Let's import toast from sonar. Let's import button.
Let's import everything we need from our card component, including the content, description, header and title. And let's go ahead and import everything we need from form. That will include form, control, description, field, item, label and message. Now let's go ahead and just add some control fields. So that's the input.
We're going to need the separator to make it look good. I think this is the first time we're using the separator. It's just like any other components from our workspace UI components. And let's add text area because we will have some larger fields to fill here. And before we go any further I don't want to develop this until I actually create the widget settings absurd function.
So let's go ahead and quickly do that. So we can focus on the customization form here, but let's also focus on widget settings private convex function here. And just as we've created get one, let's go ahead and copy this now. And let's go ahead and do absurd. This will be mutation.
So make sure you change that. And instead of the arguments for this mutation, you will basically be able to modify everything that you've defined in the schema. So greet message, let's go ahead and import convex values. Besides greet message, you will also be able to modify all the default suggestions. So these ones, always double check this with your schema, right?
Basically, we can literally copy from here and you will be able to modify the WAPI settings. The only thing you won't be able to modify is the organization ID. Now in here you now have the arguments. We do the identity check, we do the organization check and now in here we fetch the widget settings and we are still going to need to do that. Let's just change the name of this to be existing widget settings because they're gonna be used for a purpose of checking if we have widget settings or not and let's simply do if existing widget settings in that case let's await context database patch existing widget settings underscore ID greet message default suggestions and wapi settings and let's of course yeah do arguments Can I just pass in the arguments here?
I can, perfect. Because they're identical, right? I didn't have to, usually we wouldn't do that because in the arguments we'd have some identifier like organization ID, right? But in this case, literally the entire values of our arguments are the values of this. You can see if I just modify slightest thing like this, I think I'm going to get errors here.
I'm not getting errors. Yeah. Okay. That kind of worries me. I thought that it's going to be strict with that, but it's not.
Okay. Then let's actually do it one by one rather than that. So greet message arguments greet message, default suggestions arguments default suggestions, WAPI settings arguments WAPI settings. And else, Let's go ahead and let's do await context database insert, widget settings, organization ID is the org ID of the current user. Greet message is arguments greet message.
Default suggestions are arguments default suggestions. And WAPI settings are arguments WAPI settings. There we go. Now we have our absurd function and we can go back inside of the customization form. Now let's go ahead and let's simply create the widget settings schema for all widget settings.
So those are going to be z.object, greetMessage, z.string with a minimum value of 1, greetingMessage is required. DefaultSuggestions which will be an object of three items inside and WAPI settings which is an object of assistant ID and phone number. Each option inside of default suggestions and inside of WAPI settings are optional. The only thing required is the greet message. Now let's go ahead and create type widget settings and let's remap that to document, import that from workspace back and generate it data module and map it to widget settings so we don't have to type this every time.
Now let's create interface customization form props, initial data, widget settings or null. Let's Let's export CustomizationForm and let's assign CustomizationFormProps InitialData There we go. Now in here, let's go ahead and let's return div form. Now we can go back inside of the customization view component and we can import the customization form and you shouldn't have any type errors here. Now I do want to slightly just go back here.
And I also want to import WAPI plugin using use query, API, private, plugins, get one, service, WAPI. Let me see, oh, okay, WAPI like this. Then let's also mark this if WAPI plugin is undefined. We can actually extract this And then use is loading. So it's clear what this is doing.
And now that we have that, the only reason I'm using it for is so that I can pass it here. So has a VAPI plugin. And let's simply go ahead and do the following WAPI plugin. Now let's go back inside of customization form and let's go ahead and let's add the new prop which is HasWAPI plugin And we're going to make it a required Boolean. And now we can focus on this.
Instead of here, the first thing I want to do is create upsert widget settings, use mutation and import use mutation from convex react and passing API. Do we have API import API from workspace back and generated API. And then in here, API dot private dot widget settings dot absurd, which we've just created. And then in here api.private.widgetSettings.absurd which we've just created. Now let's define the form, useForm, and let me just create the type form schema to be Z dot infer type of widget settings schema.
Now I have the type form schema and I can just easily add it here. Let's go ahead and let's add a resolver. Zod resolver, pass in the widget settings schema and for the default values set the greet message to check if we have initial data question mark dot greet message or use hi how can I help you today Then for the default suggestions, suggestion one, initial data, question mark, default suggestions, dot suggestion one, or empty string? And now do this for suggestion 2 and 3. For the WAPI settings, similarly add assistant ID, initial data, WAPI settings, assistant ID or empty string, and same thing for phone number.
And That's how we define our initial settings. Now let's go ahead and let's create our onSubmit method. So onSubmit will be an asynchronous method which accepts values, type of form schema. Let's open try, let's open catch. In the catch let's do toast.error, something went wrong.
And for development mode let's also have the error here in console, error, the error. Instead of try, let's go ahead and do const wapi-settings first, so we process them correctly. So the assistant ID will be data wapi-settings, my apologies this isn't data, it's values, dot assistant ID is equal to none. So why am I checking if it's equal to none? Because we're going to add our select field and in this select field we're going to allow our user to select literally value none and this will be used to kind of reset if they just don't want to use an assistant ID.
So they can have WAPI connected, but maybe they just want to turn it off for whatever reason. So that's why I'm checking. If they specify value none to the back end, I'm not going to send literal string and none because to the back end that is an ID. That's a value. So I know that that's going to be just an empty string.
Otherwise values WAPI settings assistant ID. And can I maybe use widget settings, WAPI settings type in here? So if I misspell something, I get an error. I recommend you do that as well. Then copy this and change all of these instances to phone number and this way we allow our user to reset values of the assistant id and phone number if needed.
And now we can do await absurd widget settings, greet message, arguments, my apologies, values, greet message, default suggestions, values, default suggestions, and WAPI settings from the constant above. After that, toast, success, widget settings saved. Widget settings saved. Great. Now let's go ahead and let's develop our form.
So in here I'm going to use the form which we imported and I'm going to spread the form. Now in here I'm going to add a native form element with a class name space Y6 on submit form handle submit on submit then I'm going to add card card header card title general chat settings then card description, configure basic chat widget behavior and messages. Outside of card header I will add card content. Card content will have form field which is a self-closing tag. And let's just add card content space y6 class.
The form field is going to have a prop control of form.control. It's going to control the value greetMessage. And in order to do that, we have to render an input field. So let's do the composition for this. Form label, greeting message, form control.
Let's just misspell, fix the typo. And we're going to use the text area self-closing component. Spread the entire field inside. Give it a placeholder of welcome message shown when chat opens and give it the rows three. And already you will see that we have this initial placeholder in case user didn't type anything.
Hi, how can I help you today? Now let's go ahead outside of form control. Let's add form description here. The first message customers customers see when they open the chat and form message. My apologies.
Yes. For message. This will display the errors if there are any. Now let's go ahead outside of this self-closing form field and let's render the separator component. Like this.
So now we have the separate now let's open a div let's give the div a class name space y4 let's open another div to enclose some elements here Let's add an h3 element default suggestions. Let's go ahead and give this a class name margin-bottom-4 and text small. Now we have the default suggestions heading. Inside of here let's add a paragraph and let's quickly describe what suggestions are for. Quick reply suggestions shown to customers to help guide the conversation.
Give this paragraph a class name of margin-bottom-of-4, text-muted-foreground, and text-small. There we go. And now in here, outside of this div, outside of this paragraph, another div with a class name, space Y4. And instead of here, you can copy this form field. And you can paste it inside of the first space here inside of this div.
Let's go ahead and just indent this. This one is going to control default suggestions dot suggestion one. So let's add suggestion one. And instead of using text area, it's going to be using input. Because this will be a shorter message.
And let's do for example, how do I get started? And no need for the description. It's already pretty descriptive. And now let's go ahead and Let's copy this form field. Let's paste it below.
This will be suggestion two. Suggestion two. And in here you can write something else just to make it look better. What are your pricing plans? And then let's do suggestion three.
So I copied this again, suggestion three here, and let's go ahead and do, I need help with my account. There we go. Quick reply suggestions shown to customers to help guide the conversation. Now let's go outside of this card here and this will be WAPI settings but only if has WAPI plugin. So if we don't have WAPI plugin, we are not going to show this.
So let me just extract this prop because we added it additionally so you need to add it too. If we have VAPI plugin, let's go ahead and add card and let me just copy my card header from here so I don't repeat myself. Instead of general chat settings, this will be voice assistant settings. And in the description here, let's go ahead and do configure voice calling features powered by WAPI. So now this will only be visible to you if you have connected your voice assistant.
So if you disconnect, you will not have this widget settings. So I would highly recommend that you have this connected. In case you forget your API keys, you can always just head to AWS. Let me just log in. So head inside of the Secrets Manager.
In here, the latest one will be, well, the latest one you added. So click on your secret and click retrieve secret value. And in here you have your private and public API key. So you can see if I click this connect here and remove my plugin and go inside of widget customization, widget settings are not visible. But if I then go ahead and connect and let me just copy the private API key and add it into the private API key field and do the same thing with the public.
This way you don't always have to have the dashboard open. There we go. If the numbers can fetch, you configured correctly and you can now go inside of your settings and there we go. You have a voice assistant settings. So make sure you have that so you can actually see what we're developing here.
And now, outside of this card header here, let's go ahead and add card content. Let's go ahead and do class name, space y6. Let me just see what part was not indented correctly. This is not indented correctly and this and for now let's do to do VAPI settings Because I just want to wrap up this form by adding it a div and adding a button save settings and giving it a disabled prop if form form state is submitting is true and give this a type of submit. And let's give this a class name flex and justify and.
And now in your settings down here you will have the save settings button. And this should already work even if this isn't developed. So what I suggest you do is go to convex.dev. Let's log in here and let's specifically go inside of our dashboard. So echo tutorial here.
Instead of your data, again if this is closed sometimes it can look like this. So you can just click on tables and it will open. Find widget settings. And right now there are none. So if I go ahead and change this to Hulo and change this to Suggestion 1, Suggestion 2 and leave the 3 empty and click Save Settings.
Widget Settings Saved. If I refresh, let's see. It's working. I don't even need to check database because if I can refresh and the settings stayed, it means they are loaded correctly. But yes, we can see them here, suggestion one, suggestion two, greet message, organization ID, and you can see how WAPI settings are just empty because we haven't done any.
So if your absurd data is correct inside of the widget settings, it should work. If something's not working, double check your absurd mutation here. And double check your get one mutation as well. Maybe something's wrong there. Or if you're not seeing values here, it could be that inside of your customization form you have forgot to fill the default values right make sure you're using the initial data that you pass from the customization view widget settings right here great Now let's go ahead and let's develop the UI for adding the WAPI plugin form fields.
So since this is a pretty large component and we're going to need to load WAPI numbers again. I want to separate this in its own component. So inside of components here create wapi-form-fields.tsx. And now let's create an interface wapi-form-fields. Wapi-form-field-props.form will be a type of use form return from React hook form and passing z.infer and then passive typeof and okay this is what we're gonna do.
First import Zot, Then go back inside of customization form. And let us export form schema. And then you will be able to just add form schema from .//customizationform. And you no longer need Zod. So this is a type.
I'm not sure if this is maybe a circular import now. I think it's okay. Worst case scenario, you can just copy the entire widget settings schema. And let's add disabled optional boolean. Now let's export const wapi form fields.
Let's go ahead and let's assign wapi form field props. Let's get the form. Let's get disabled. And in here the first thing we're going to do is fetch our WAPI assistance from our hook, which we have optimized in the very first few minutes of this chapter. Let's get the data, which we can remap to assistance, and this loading, which we can remap to assistance loading.
Let's duplicate this. Let's do use phone numbers, use MAPI phone numbers from the same import, phone numbers and let's do phone numbers loading. Perfect. Now let's go ahead and let's return an empty fragment and inside of here we need to again add our form elements. So let me just go ahead and let's add form control, description, field, item, label and message.
But besides that we also need our select element. Select content, item, trigger and value. Once you've done that, you can go inside of your customization form and just copy a form field so it's easier. So we don't have to write it all over again. And let's go ahead and let's add a form field within these fragments here.
Let me just fix the indentation. And what we're going to control first is WAPI settings.assistantId. So let's change this to be voice assistant. Instead of text area, we're going to be using select. So remove the placeholder and remove the rows.
Now select works a little bit differently. For starters, it is not a self closing component and it's not going to have all the elements of the field. It will only have field dot value And it will have on value change. So field.onChange here. It's going to be disabled if assistance is loading or if the form is disabled overall.
Using this prop right here. But the composition behind this select is a little bit different. So for now you can actually remove the form control. And remove the description and remove the message. So just have form item and then form label and then select here and then inside add form control and then select trigger and then select value.
Now the select value is actually a self-closing tag which is going to have a placeholder which will depend if assistants are loading in that case it's going to be loading assistants otherwise it will be select an assistant And then let's go ahead outside of form control and let's add select content. And let's do a composition for that. So this is just composition of how you build select components within a form using ShadCN UI. These are just the rules, as simple as that. In case you're wondering why am I not explaining why this is outside of form control, I don't know why it's outside of form control.
It's the rules. It probably has some sense, but I don't really learn that much in depth. It's enough for me to know how I have to do it and maybe check the documentation if I mess it up. But I don't go into such depths of learning exactly why select content composition has to be outside of form control. I'm sure there's some logical explanation, but I just want to get it to work.
So inside of select content, we add select items. Select item. First one will be our none option with the value none. And I hope now it's clear. We're going to allow the user to select this.
This should render now. Why is it not? Oh, because we are not passing the WAPI form fields. Let's do that. Instead of the customization form Down here we have to do WAPI settings.
Let's now do WAPI form fields. And pass in disabled. Actually we don't need disabled prop because we have the entire form prop here. So import WAPI form fields. And inside of here, we don't need the disabled because we have the form And we can just define our disabled.
Const disabled form form state is submitting. There we go. Alright, now that you have that rendered, you will see that you have an option to select none. And if you click save settings, inside of our on submit method here, in the customization form, you understand why we had to do this. We have to check if the user selected none, we shouldn't submit this literal string to the database because in the database eyes, this is a completely valid value, right?
We know that that means empty string. But as far as I know, Select component doesn't offer an option to do this. So we have to add one option ourselves and manually control its value. So that's why we did that whole thing. Great.
Now we have this select item. And now let's render the rest of the select items which can be as many assistant as the user has so assistants dot map get the individual assistant and Inside of here render the select item Assistant dot name or unnamed assistant and then let's go ahead here and let's add a space and let's render assistant.model?model or unknown model dot model question mark dot model or unknown model and in here give it a key of assistant dot id and value of assistant dot id And now you will see that you're able to load Tom and Riley and their respective models. So you can differentiate between them. Perfect. And outside of this select here, Add a form description.
And simply write the WAPI assistant to use for voice calls. And form message to display if any form errors are here. Perfect. And now we can literally copy this form field. And we can paste it.
And we can simply modify it for the phone numbers. So WAPI settings, phone number. This will be display phone number. This will be phone numbers loading. Loading.
Do I have it? Do I not have it? Phone numbers loading. I'm having a typo somewhere, but I don't see it. Okay, phone numbers loading.
There we go. Loading phone numbers. Select a phone number. This will be unknown and this will be unnamed. We are not iterating over assistants, we are iterating over phone numbers here.
So this is a phone, so phone.id, phone.id and for the value we can actually use either phone.number or phone.id and in here check if we have phone.number instead of name and then it's going to be unknown or I mean in the second option it's phone.name or unnamed. And this description will say phone number to display in the widget. And there we go. You can now select your assistant and you can select your display phone number and click save settings. And if we've done this correctly, let's first refresh to see if we've done it correctly.
We did, as you can see. So inside of here now, if I go ahead and click in WAPI settings, you can see how we store the exact assistant ID and then you can see how this will help us because remember we have this use wapi which we created in chapter 6 I believe where we demonstrated how wapi works and remember we have to pass the api key which we now have because we store it inside of plugins and then we have the secret name and then we load AWS and then we have the customer's API key but one more thing we needed was Vapi's assistant ID And we now have that as well. So this hook is almost ready to be used again, this time with our customers information instead of ours. And that will mark the end of our white labeling journey, which I say was very exciting to do something completely new we've done on this channel. Excellent!
So I believe that's all I wanted to do in this chapter here. One thing I'm a little bit concerned about is importing this form schema from customization form, which then imports the WAPI form fields. And I have no idea how this import got here, we don't need it. I am kind of concerned about this. We will see if CodeRabbit has anything to say about this.
Okay I'll just do some research myself just for a second. Or you know I don't need the research I know what we can do. Let's just copy the widget settings schema and inside of customization here let's go ahead and let's create constants.ts. Go ahead and paste widget settings schema here. Import Zod.
Maybe rename this to Schemas, just like that. So in here, we keep Widget Settings Schema and in the customization, go ahead and create Types.ts, import widget settings schema from our newly created schemas and export type form schema Z, again from Zaha.Infer, type of widget settings schema. There we go. And now we have this form schema. Now instead of our customization form, remove this.
Remove form schema and use the form schema from types and use the widget settings schema from schemas. There we go, our newly created fields inside of the customization module here. So that's the customization form fixed and now instead of WAPI form fields we no longer have to do this circle or import we can just import from types. There we go. I think everything should still work perfectly fine.
This was only settings change after all. Let's change this And let's change this to Riley and let's change this to none and change this to something new. Save settings. Refresh. Everything works.
Amazing, amazing job. I believe that marks the end of this chapter. So we added, we fixed WAPI data hooks. We added widget settings schema, functions, customization view, customization form. And now let's go ahead and review our changes.
So this is 26 widget customization. I'm going to go ahead and stage all of these changes. 26 widget customization. Let's commit. Let me open a new branch.
26 widget customization and let's publish the branch. Now let's go ahead and let's open a new pull request. Let me just go ahead. I just have to do some things for this branch to appear here. There we go.
26 widget customization. Let's create pull request. And let's wait for the review to start. So I thought that instead of using the pull request review, how about we use the code extension review instead. So I'm going to go ahead and click review all changes using the free CodeRabbit extension which you can install as well.
Of course you don't have to do this. I just want to see if we made any crucial mistakes because CodeRabbit really does catch a lot of mistakes that we do. So this time instead of using it in the pull request here I have stopped it here so I'm going to use it via the CodeRabbit extension instead. You can find the extension by searching for CodeRabbit and it's a completely free AI code review and you just have to connect your account that you are usually using for GitHub. And here we have the CodeRabbit review.
So Don't worry, I will not do any changes here. I already told you to start a new branch and I already told you to open a pull request. So don't worry, I will only use this as a guide on what to do in the next chapter. But we do have some mistakes here. Starting with the first one in the WAPI form fields here, I have a completely incorrect loading state variable, as you can see.
I'm using assistance loading, where I should be using phone numbers loading. So for now I'm just going to reject the changes even though they are completely correct simply because I don't know if you already opened your branch. I don't want to make this complicated for you. I will just remember it for the next chapter. Excellent.
So that's one error here. Let's see. Add null checks and error handling for data fetching here. I'm not sure if we have to do that because of the nature of how these hooks work from the inside because I think they are already safe. Not sure.
Now let's go ahead and go instead of the customization form and see improve type safety for optional initial data properties. Let's see the suggestion here. The code assumes initial data properties exist without proper null checking. Let me see. Default suggestions.
The default suggestion object always exists, so that's not a problem. Same with WAPI settings. So if initial data exists, that's the widget settings schema the default suggestions object must exist inside these options are empty but the object itself exists so that is good Now let's go ahead and go inside of the customization view here. Let's see the issue here. Error handling.
All right. Yes, we could add error handling, but for now I'm okay with just handling the loading state. And I think there's one more comment here. Improve type safety for organization ID. Oh yes, this, yeah, we could do that, but since this is coming from Clerk, it's pretty safe already, and we've already done it this way everywhere, so I don't want to change anything.
I want to be consistent and this is pretty okay. Actually, it works fine. Excellent. So you can see this extension actually works super well as well and you can even use the their magic AI button to fix all the issues. They also have some nitpick comments but you can see we can just improve the structure or enhance error handling, duplicated authentication logic, things we already know.
Excellent, yes, so you can use the Code Rabbit extension as well. I didn't accept any of the changes for a simple reason. We already opened the branch. I don't want to confuse you. We're just going to fix the loading issue in the next chapter.
And for now we can merge this pull request so that we have history of this chapter. This is 26 widget customization. And we can now go back inside of our main branch and we can resynchronize our changes here and then we can go back inside of the source control and let's double check in the graph. There we go after WAPI data 26 widget customization everything is good. Make sure that you're on the main branch you can click on this button as many times as you want and let's go ahead and mark this as completed that marks the end of this chapter and see you in the next one.